showModal is not defined when sharing State Between Livewire And Alpinejs with #entangle - laravel

Basically I'm using the #entangle to share the state between my Livewire component and my Alpine component.
<div x-data="{ open:#entangle('showModal') }" #keydown.escape="showModal=false">
<button type="button" class="block p-2 btn-light-green" #click="showModal=true">Añadir Mezcla</button>
<!--Overlay-->
<div class="overflow-auto" style="background-color: rgba(0,0,0,0.5)" x-show="showModal" :class="{ 'absolute inset-0 z-10 flex items-center justify-center': showModal }">
<!--Dialog-->
<div class="bg-white w-11/12 md:max-w-md mx-auto rounded shadow-lg py-4 text-left px-6"
x-show="showModal"
#click.away="showModal=false"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-300"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0">
<!-- Title -->
<div class="flex justify-between items-center mb-3">
<p class="text-xl font-bold">Añadir una mezcla</p>
<div class="cursor-pointer z-50" #click="showModal=false">
<svg class="fill-current text-black" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
<path d="M14.53 4.53l-1.06-1.06L9 7.94 4.53 3.47 3.47 4.53 7.94 9l-4.47 4.47 1.06 1.06L9 10.06l4.47 4.47 1.06-1.06L10.06 9z"></path>
</svg>
</div>
</div>
<!-- Content -->
<livewire:mix.user.create-mix-form/>
</div>
<!-- /Dialog -->
</div>
<!-- /Overlay -->
</div>
This is my Livewire Component:
<?php
namespace App\Http\Livewire\Mix\User;
use App\Models\Mix;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use Livewire\WithPagination;
class ShowUserMixes extends Component
{
use WithPagination;
protected $listeners = [
'deletedMix' => 'render',
'addedMix' => 'render'
];
public $showModal = false;
public function closeModal()
{
$this->showModal = false;
}
public function deleteUserMix($mix_id, $user_id)
{
if (Auth::user()->id !== $user_id) {
return false;
}
$mix = Mix::find($mix_id);
$mix->delete();
$this->emitSelf('deletedMix');
}
public function render()
{
return view('livewire.mix.user.show-user-mixes', [
'mixes' => Auth::user()->mixes()->where('active', 1)->paginate(10)
]);
}
}
This is the error I'm getting at the console:
Uncaught ReferenceError: showModal is not defined
at eval (eval at saferEval (alpine.js?df24:1701), :3:36)
at saferEval (alpine.js?df24:112)
at Component.evaluateReturnExpression (alpine.js?df24:1581)
at eval (alpine.js?df24:1557)
at Array.forEach ()
at Component.resolveBoundAttributes (alpine.js?df24:1530)
at Component.initializeElement (alpine.js?df24:1446)
at eval (alpine.js?df24:1430)
at eval (alpine.js?df24:1420)
at walk (alpine.js?df24:84)
I don't know what might me happening here, I'm using the #entangle properties as the Livewire docs do.
https://laravel-livewire.com/docs/2.x/alpine-js#extracting-blade-components

When you #entangle a property in AlpineJS, you're making a reference form your Alpine Property to another property in your Livewire Component.
Your issue is at the top of your modal definition, in your x-data declaration.
If you use x-data="{ open: #entangle('showModal') }", your showModal property will be bound inside alpine with the name open.
You only need to change the name of your definition:
<div
x-data="{ showModal: #entangle('showModal') }"
<!-- Other attributes here -->
>
<!-- Your modal goes here -->
</div>

You are trying to set and evaluate your livewire variable showModal when you should be setting and evaluating the property you set with Alpine which is open.
Try:
<div x-data="{ open:#entangle('showModal') }" #keydown.escape="open=false">
<button type="button" class="block p-2 btn-light-green" #click="open=true">Añadir Mezcla</button>
<!--Overlay-->
<div class="overflow-auto" style="background-color: rgba(0,0,0,0.5)" x-show="open" :class="{ 'absolute inset-0 z-10 flex items-center justify-center': open }">
<!--Dialog-->
<div class="bg-white w-11/12 md:max-w-md mx-auto rounded shadow-lg py-4 text-left px-6"
x-show="open"
#click.away="open=false"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-300"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0">
<!-- Title -->
<div class="flex justify-between items-center mb-3">
<p class="text-xl font-bold">Añadir una mezcla</p>
<div class="cursor-pointer z-50" #click="open=false">
<svg class="fill-current text-black" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
<path d="M14.53 4.53l-1.06-1.06L9 7.94 4.53 3.47 3.47 4.53 7.94 9l-4.47 4.47 1.06 1.06L9 10.06l4.47 4.47 1.06-1.06L10.06 9z"></path>
</svg>
</div>
</div>
<!-- Content -->
<livewire:mix.user.create-mix-form/>
</div>
<!-- /Dialog -->
</div>
<!-- /Overlay -->
</div>

Related

I can't get session value in script setup vue3 composition API

I managed to make a new project with inertiajs new version and Laravel 8 and vue 3. I want to make a toast component which will show when flash message has value in the handleinertia middleware.
My only problem is I can't seem to get the session value it is null all the time in usePage();. BUT the
{{ $page.props.flash.message }}
in Notification.vue displays it.
My question is, how can I get the session over router.on("finish") so that I can only show it when there is session.
I left some comments in the code, I can't explain well in English :-C
Controller:
return redirect()->route('users')->with('message', 'User created');
HandleInertiaRequests.php
public function share(Request $request): array
{
return array_merge(parent::share($request), [
'flash' => [
'message' => fn () => $request->session()->get('message'),
'alert_message' => fn () => $request->session()->get('alert_message'),
'error' => "check",
],
]);
}
Notification .vue
<script setup>
import { ref, watch, onUnmounted, onMounted } from 'vue'
import { CheckCircleIcon } from '#heroicons/vue/24/outline'
import { XMarkIcon } from '#heroicons/vue/20/solid'
import { usePage, router } from '#inertiajs/vue3'
const page = usePage();
const show = ref(false)
let removeFinshEventListener = router.on("finish", () => {
// I ALWAYS GET NULL VALUES HERE BUT IN THE HTML BELOW I GET THE SESSION
console.log(page.props.flash, page.props.flash.message)
show.value = true
if (page.props.flash.message) {
}
});
onUnmounted(() => removeFinshEventListener());
function remove(index) {
toast.remove(index);
}
</script>
<template>
<!-- Global notification live region, render this permanently at the end of the document -->
<div aria-live="assertive" class="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6">
<div class="flex w-full flex-col items-center space-y-4 sm:items-end">
<!-- Notification panel, dynamically insert this into the live region when it needs to be displayed -->
<transition
enter-active-class="transform ease-out duration-300 transition"
enter-from-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2" enter-to-class="translate-y-0 opacity-100 sm:translate-x-0"
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div v-if="show" class="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5">
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
<CheckCircleIcon class="h-6 w-6 text-green-400" aria-hidden="true" />
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium text-gray-900">Successfully saved!</p>
// THE SESSION MESSAGE DISPLAYS HERE BUT NOT IN THE usePage(); ABOVE
<p class="mt-1 text-sm text-gray-500">{{ $page.props.flash.message }}</p>
</div>
<div class="ml-4 flex flex-shrink-0">
<button type="button" #click="show = false" class="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
<span class="sr-only">Close</span>
<XMarkIcon class="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>
</div>
</div>
</transition>
</div>
</div>
</template>
Layout.vue
<template>
<div class="min-h-full">
<Notification></Notification>
<mobile-sidebar
:sidebarOpen="sidebarOpen"
#close="sidebarOpen = false"
></mobile-sidebar>
<!-- Static sidebar for desktop -->
<sidebar></sidebar>
<!-- Main column -->
<div class="flex flex-col lg:pl-64">
<mobile-header #sidebarOpen="sidebarOpen = true"></mobile-header>
<main class="flex-1">
<slot></slot>
</main>
</div>
</div>
</template>
<script setup>
import { ref, watch, onUpdated } from "vue";
import MobileHeader from "#/Shared/MobileHeader";
import MobileSidebar from "#/Shared/MobileSidebar";
import Sidebar from "#/Shared/Sidebar.vue";
import Alert from "#/Shared/Alert.vue";
import { usePage } from '#inertiajs/vue3'
const sidebarOpen = ref(false);
</script>

already four days now, still have not reslove this challenge

What am i not doing right? i'm woking on a livewire project that needs to get the grade_id's to show in show-component.blade.php, from Modules table which is related to a Grades table. My challenge is that, it only show a particular id, like the one in the mount section, so if i change it to 2 it will only show id's that are 2's, though thats really what i wanted but i can be change it manually.
use App\Models\Grade;
use App\Models\Module;
use Livewire\Component;
class ShowComponent extends Component
{
public $modules;
public $grades;
public function mount()
{
$this->modules = Module::all()->where("grade_id", "1");
}
public function render()
{
return view('livewire.show-component');
}
}
=========================================================================
show-component.blade.php
#foreach($modules as $module)
<div class="flex flex-col grid-cols-12 md:grid text-gray-50">
<div class="flex md:contents">
<div class="relative col-start-2 col-end-4 mr-10 md:mx-auto">
<div class="flex items-center justify-center w-6 h-full">
<div class="w-1 h-full bg-green-500 pointer-events-none"></div>
</div>
<div class="absolute w-6 h-6 -mt-3 text-center bg-green-500 rounded-full shadow top-1/2">
<i class="text-white fas fa-check-circle"></i>
</div>
</div>
<div class="w-full col-start-4 col-end-12 p-4 my-4 mr-auto bg-green-500 shadow-md rounded-xl">
<h2 class="mb-1 text-lg font-semibold">
{{ $module->name }}
</h2>
<p class="w-full leading-tight text-justify">
<a href="{{ url('storage/videos'.$module->video) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd" />
</svg>
</a>
</p>
</div>
</div>
</div>
#endforeach
==================================================================
Module's Model
public function grade()
{
return $this->belongsTo(Grade::class, 'grade_id', 'id');
}
===============================================================
Grade's Model
public function curricula()
{
return $this->belongsTo(Curriculum::class, 'curricula_id', 'id');
}
public function module()
{
return $this->hasMany(Module::class);
}
If you want all Module records stored in your database table, you want to remove the where condition which is limiting the return value.
$this->modules = Module::all();
If you want just the grade_ids from your modules table, use pluck:
$grade_ids = Module::pluck('grade_id');
I think what you want is ..
Add
public $grade_id;
Then in mount
$this->modules = Module::where("grade_id", $this->grade_id)->get();
When you load your component you can pass grade_id to it

I can't dispatch event in Alpinejs

I am using Laravel 8, Alpinejs and Livewire and having this problem:
This is my index file:
#extends('dashboard')
#section('content')
<header class="max-w-7xl mx-auto bg-white flex items-center justify-between">
<div class="mx-9 py-6 mt-4">
<h1 class="text-3xl font-bold text-gray-900">
Boards
</h1>
</div>
<div class="mx-9 py-6 mt-4">
<a href="#" #click="
$dispatch('custom-event')
"
class="w-28 text-base text-white bg-blue hover:text-gray-50 hover:bg-blue-hover rounded-xl py-2 px-3 leading-none transition ease-in duration-150 text-center">
Create new board
</a>
</div>
</header>
<livewire:boards-table
:boards="$boards"
/>
<livewire:create-board />
#endsection
When I click the "Create new board" button in the header tag, I want the create-board livewire component to be displayed. So I used #click="$dispatch('custom-event').
And here is my create-board file:
<div
x-cloak
x-data="{ isOpen: false }"
x-show="isOpen"
#keydown.escape.window="isOpen = false"
#custom-event.window="isOpen = true"
class="fixed z-20 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true"
>
<div class="flex items-end justify-center min-h-screen">
<div x-show.transition.opacity="isOpen" class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
aria-hidden="true">
</div>
<div x-show.transition.origin.bottom.duration.300ms="isOpen"
class="modal bg-white rounded-tl-xl rounded-tr-xl overflow-hidden transform transition-all sm:max-w-lg sm:w-full">
<div class="absolute top-0 right-0 pt-6 pr-6">
<button #click="isOpen = false" class="text-gray-400 hover:text-gray-500">
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd" />
</svg>
</button>
</div>
<div class="bg-white sm:p-6 my-3">
<h3 class="text-center text-xl font-bold text-gray-900">Create a new board</h3>
<form wire:submit.prevent="createBoard" action="#" method="POST" class="space-y-4 px-4 pt-6">
<div>
<input wire:model.defer="boardName" type="text"
class="w-full bg-gray-100 rounded-xl placeholder-gray-900 border-none font-semibold px-4 text-sm"
placeholder="Board name">
</div>
<div>
<input wire:model.defer="urlName" type="text"
class="w-full bg-gray-100 rounded-xl placeholder-gray-900 border-none font-semibold px-4 py-2 text-sm"
placeholder="Short name (used in board URL)">
</div>
<div class="flex items-center justify-end">
<button type="submit"
class="text-center w-44 bg-blue font-bold h-11 text-sm text-white rounded-xl border border-blue hover:bg-blue-hover transition duration-150 ease-in px-6 py-3 mr-4 mt-2">Create new board</button>
</div>
</form>
</div>
</div>
</div>
</div>
But when I click on "Create new board" button, nothing happens.
Create-board component is still rendered in the browser.

Alpine.js bind the change of select back to x-data

I have two anchor tags whose #click directives? update my select options. I would like when the options are changes to update the value of activeTab to be either 0 or 1. I've been trying #change but no joy. Thanks in advance
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine#v2.x.x/dist/alpine.min.js"></script>
<div x-data="{activeTab : window.location.hash ? window.location.hash.substring(1) : 0, lessons:[{id:0,room:'online',description:'Online description'},{id:1,room:'in class',description:'in class description'}]}" x-init="select = lessons[0].room" class="w-full">
<nav class="w-full flex flex-no-wrap justify-between mb-8">
<template x-for="lesson in lessons">
<a href="#" #click.prevent="activeTab = lesson.id; window.location.hash = 0; select = lesson.room" class="focus:outline-none focus:text-teal-800 hover:text-teal-800 meta bold py-1 uppercase mr-1 flex items-center justify-between text-lg w-1/2 border-b-4 focus:border-teal-800 hover:border-teal-800 border-teal-600 tracking-widest text-teal-600"><span x-text="lesson.room"></span><svg class="w-6 h-6" width="6" height="6" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg">
<path d="m8.5.5-4 4-4-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" transform="translate(6 8)" /></svg></a>
</template>
</nav>
<template x-for="lesson in lessons" :key="lesson.id">
<div x-show="activeTab === lesson.id">
<p x-text="lesson.description" class="text-gray-800 mb-6">Online classes are streaemed to your device. You can atned a yoga class wherever there is a why-fi</p>
</div>
</template>
<form action="">
<fieldset class="border p-4">
<legend class="text-center text-xs uppercase tracking-widest text-orange-800 px-2">choose a classroom</legend>
<select class="relative uppercase text-lg tracking-widest text-teal-800 w-full border border-teal-800 px-5 py-4 focus:outline-none focus:border-shadow rounded" name="" id="" x-model="select">
<template x-for="lesson in lessons" :key="lesson.id">
<option :id="lesson.id"><span x-text="lesson.room"></span></option>
</template>
</select>
</fieldset>
</form>
</div>
Here is a simplified version of your code that works. I didn't really want to change your code, but I had to simplify it a bit to comprehend it. For example, activeTab and select seemed redundant, and I didn't understand the part about window.location.hash.
<script defer src="https://unpkg.com/alpinejs#3.x.x/dist/cdn.min.js"></script>
<div
x-data="{
activeTab: 0,
lessons: [
{ id: 0, room: 'online', description: 'Online description' },
{ id: 1, room: 'in class', description: 'In class description' }
],
}"
>
<nav>
<template x-for="lesson in lessons">
<a
href="#"
#click.prevent="window.location.hash = activeTab = lesson.id"
:style="activeTab === lesson.id ? 'font-weight:bold' : 'text-decoration:none'"
>
<span x-text="lesson.room"></span></a>
</template>
</nav>
<template x-for="lesson in lessons">
<div x-show="activeTab === lesson.id">
<p x-text="lesson.description"></p>
</div>
</template>
<form>
<fieldset>
<legend>choose a classroom</legend>
<select x-model.number="activeTab">
<template x-for="lesson in lessons">
<option :value="lesson.id" x-text="lesson.room"></option>
</template>
</select>
</fieldset>
</form>
</div>
JSFiddle
I believe the problem was a strict equality comparison (===) between values which were sometimes strings and sometimes integers. For example, "1" === 1 is never true.
I think there were two problems with your original code:
select wasn't declared part of x-data, thus it wasn't christened a "reactive" property, thus changing the value of the <select> had no actual effect
fix this by adding something like select: null to your x-data
that aside, there was still nothing to trigger the update of activeTab when the <select> was changed
binding lesson.id to the <option> values and the <select>'s value to activeTab with x-model.number (forgoing the select variable entirely) is how I chose to address this problem
this way the x-shows for the active tab trigger directly from a change to the <select>'s value
This didn't apply in your code as originally written, but keep in mind that an x-model on a <select> will always return strings, unless you use x-model.number to coerce to integer.

Change value of select option by clicking anchor tag with Alpine.js

I'm using x-data to dynamically build my HTML. I have two anchor tags which act as tab buttons to'x-show' a paragraph depending on which link is clicked. I also would like that anchor tag to select an option on the form's select element. It kind of works when you starts clicking on the buttons but initially the select options are empty?
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine#v2.x.x/dist/alpine.min.js"></script>
<div x-data="{activeTab : window.location.hash ? window.location.hash.substring(1) : 0, lessons:[{id:0,room:'online',description:'Online description'},{id:1,room:'in class',description:'in class description'}]}" class="w-full">
<nav class="w-full flex flex-no-wrap justify-between mb-8">
<template x-for="lesson in lessons">
<a href="#" #click.prevent="activeTab = lesson.id; window.location.hash = 0; select = lesson.room" class="focus:outline-none focus:text-teal-800 hover:text-teal-800 meta bold py-1 uppercase mr-1 flex items-center justify-between text-lg w-1/2 border-b-4 focus:border-teal-800 hover:border-teal-800 border-teal-600 tracking-widest text-teal-600"><span x-text="lesson.room"></span><svg class="w-6 h-6" width="6" height="6" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg">
<path d="m8.5.5-4 4-4-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" transform="translate(6 8)" /></svg></a>
</template>
</nav>
<template x-for="lesson in lessons">
<div x-show="activeTab === lesson.id">
<p x-text="lesson.description" class="text-gray-800 mb-6">Online classes are streaemed to your device. You can atned a yoga class wherever there is a why-fi</p>
</div>
</template>
<form action="">
<fieldset class="border p-4">
<legend class="text-center text-xs uppercase tracking-widest text-orange-800 px-2">choose a classroom</legend>
<select class="uppercase text-lg tracking-widest text-teal-800 w-full border border-teal-800 px-5 py-4 focus:outline-none focus:border-shadow rounded" name="" id="" x-model="select">
<template x-for="lesson in lessons">
<option x-text="lesson.room"></option>
</template>
</select>
</fieldset>
</form>
</div>
I added:
x-init="select = lessons[0].room"
to the parent of the component which set the select to the initial value
A better way would be to add this onto the select
<select x-model="foo" x-init="foo = $el.options[$el.selectedIndex || 0].value">

Resources