How to add classes to an element outside of AlpineJS element - javascript

I have a simple question but it's driving me mad to work out.
I'm using AlpineJS to show and hide content.
Normally with alpine, the button and content will be in the same element, but here I have it outside of it. This works.
But I cannot add classes to both the button and content div when the content is open or closed.
Can anyone guide me please.
<!-- button -->
<div class="mt-4" x-data="{id: 1}">
<button
#click="$dispatch('open-dropdown',{id})"
type="button"
class="bg-white hover:bg-gray-50"
:class="{ 'bg-green-400': open, 'bg-gray-200': !(open) }">
I'm the button
</button>
</div>
<!-- popup 1 -->
<div x-data="{ open: false }"
x-show="open"
#open-dropdown.window="if ($event.detail.id == 1) open = true"
#click.away="open = false">
<div
class="bg-white hover:bg-gray-50"
:class="{ 'bg-green-400': open, 'bg-gray-200': !(open) }">
I'm the content
</div>
</div>

For this you can move the open state inside the global Alpine.js' $store object that is accessible for all component on the page.
<!-- button -->
<div class="mt-4" x-data="{id: 1}">
<button #click="$dispatch('open-dropdown',{id})"
type="button"
class="bg-white hover:bg-gray-50"
:class="{ 'bg-green-400': $store.open, 'bg-gray-200': !($store.open) }">
I'm the button
</button>
</div>
<!-- popup 1 -->
<div x-data
x-show="$store.open"
#open-dropdown.window="if ($event.detail.id == 1) $store.open = true"
#click.away="$store.open = false">
<div class="bg-white hover:bg-gray-50"
:class="{ 'bg-green-400': $store.open, 'bg-gray-200': !($store.open) }">
I'm the content
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.store('open', false)
})
</script>
Since our open state in the store is a simple value, we can just replace every open with $store.open. Note that the popup 1 component still requires an empty x-data attribute.

Related

Alpinejs is not working for Modal only for Sidebar Menu

I am building a site with Laravel, tailwind css and alpinejs. I have a mobile navigation which is a offcanvas sidebar and a modal shopping cart. The responsive mobile menu works fine but I can not figure out why the modal is not showing:
Layout.blade.php which blade components are placed in :
<body class="bg-[#F8F3F0]" >
<div x-data="{ open: false, opencart: false }" #keydown.window.escape="open = false">
<x-home.cart />
<x-home.mobilenav />
<div class="w-full hexnoisegrad h-[700px]">
...
<x-home.nav />
...
In the home.nav component we have buttons that trigger the menu and modal:
...
<button type="button"
class=" ..."
#click="open = true">
<span class="sr-only">Open sidebar</span>
<x-icon.menu />
</button>
<button type="button"
class=" ..."
#click="opencart = true">
<span class="sr-only">Open Cart</span>
<x-icon.bag/>
</button>
In the home.mobilenav component we have:
<div dir="rtl" x-show="open" class="fixed inset-0 flex z-40 lg:hidden"
x-description="Off-canvas menu for mobile, show/hide based on off-canvas menu state." x-ref="dialog"
aria-modal="true">
...
And finally in the home.cart (modal) component we have:
<div x-data="{ opencart: false }" #keydown.window.escape="opencart = false" x-show="opencart" class="fixed z-10 inset-0 overflow-y-auto" x-ref="dialog" aria-modal="true">
...
I don't know why but Menu works fine and Modal (Shopping Cart) is not!
I think somehow it can not access "opencart" to check for the x-show.
If I change the x-data of cart component to:
x-data="{ opencart: true}"
Modal will open correctly for the first time but after closing the button won't make it appear again.
I Also have drop downs that are working fine with alpine.
Very sorry for long post and taking your time.
Thanks in advance.
Alpinejs documentation indicates
Properties defined in an x-data directive are available to all element children. ref
That implies a parent/child relation between the elements holding x-data and x-show.
So try adding a wrapper div to hold x-data only.
<div x-data="{ opencart: false }">
<div #keydown.window.escape="opencart = false" x-show="opencart" class="fixed z-10 inset-0 overflow-y-auto" x-ref="dialog" aria-modal="true">
...
</div>

Can you access component slot data from parent

I am trying to get the rangeText data into div outside of the component block for https://innologica.github.io/vue2-daterange-picker/advanced/#slots-demo but it only seems to appear when used in a slot.
I am trying to update the selectDateButtonText text after user selects dates.
Many thanks
<template>
<div v-click-outside="hideCalendarDropdown" class="select-dates">
<button #click="filterCalendarIsActive = !filterCalendarIsActive" :class="{ 'is-active': filterCalendarIsActive }" class="select-dates__button fw-lt-sm">{{ selectDateButtonText }}</button>
<div :class="{ 'is-active': filterCalendarIsActive }" class="calendar-wrapper">
<date-range-picker #finish-selection="datesSelected()"
v-model="dateRange"
:minDate="minDate"
:maxDate="null"
:singleDatePicker="singleDatePicker"
:opens="opens"
:showDropdowns="showDropdowns"
:autoApply="autoApply"
:ranges="ranges"
>
<div slot="footer" slot-scope="data" class="date-range-picker-footer">
{{ data.rangeText }}
<button class="clear-dates" #click="clearDate()" type="button"> Clear </button>
</div>
</date-range-picker>
</div>
</div>
</template>
Got it.
Had to $refs the component
datesSelected() {
this.selectDateButtonText = this.$refs['picker'].rangeText;
}

modal opening only for the first for loop element?

I have my for loop in Django such as its only opening the modal for the first element and nothing happens for other elements.
This is my HTML file it display all elements in a table with delete button. When clicking delete button a modal is opened but it is opened for the first element only, what should I change in this?
<table>
{% for card in cards %}
<tr class="record">
<td>{{card.number}}</td>
<td>
<button
class="hover:bg-gray-500 duration-500"
id="delete-btn"
>
</button>
<div
class="
fixed
flex
justify-center
items-center
hidden
"
aria-labelledby="modal-title"
aria-modal="true"
id="overlay"
>
............. #some lines of code
<div
class="
bg-gray-50
px-4
py-3
sm:px-6 sm:flex sm:flex-row-reverse
"
>
<a
href="{% url 'p:delete-card' card.id %}"
id="delbutton"
>
<button
type="button"
class="
w-full
inline-flex
justify-center
"
>
Delete
</button>
</a>
</div>
</div>
</div>
</td>
</tr>
{% endfor %}
My js to open the modal on button click
window.addEventListener('DOMContentLoaded',() =>{
const overlay = document.querySelector('#overlay')
const delbtn = document.querySelector('#delete-btn')
const toggleModal = () => {
overlay.classList.toggle('hidden')
overlay.classList.add('flex')
}
delbtn.addEventListener('click',toggleModal)
})
I solved it like this with the help of Data in Django for loop {% for %} not loading for Modal
I changed the id of all the buttons like this and added delete-button class!
<button class="hover:bg-gray-500 duration-500 delete-button" id="delete-btn_{{card.id}}">
</button>
Like this added card.id in overlay too.
My Jquery:
$(".delete-button").click(function() {
var id = $(this).attr("id").split("_")[1]
$(`#overlay_${id}`).show();
});
Every modal should have a different id in the for loop.
You can use something like id="modal{{card.id}}"
Then select that id
Does that help you?

vue button related event not fired

In:
https://codesandbox.io/s/upbeat-hodgkin-qjt61?file=/src/components/EditCategory.vue
the modal is shown as expected upon long click over a category:
but clicking OK does not fire the close event:
<template>
<div>
<p v-longclick="() => longClicked()" #click="longClicked()">
{{ taskItemLocal["name"] }}
</p>
<div v-if="this.showModal" #close="closeModal()">
<transition name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header"> Edit Category </slot>
</div>
<div class="modal-body">
<slot name="name"> Edit Name </slot>
</div>
<div class="modal-body">
<slot name="delete"> Delete Category </slot>
</div>
<div class="modal-footer">
<slot name="footer">
<!-- default footer -->
<!-- EVENT NOT FIRING -->
<button class="modal-default-button" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</div>
</div>
</template>
closeModal() is not called; changing showModal "directly" also fails.
You have dispatch event to parent but in parent component you have not done any thing with "close" event. Here, in GenericItem.vue I have made event listener with #close="closeBox($event)" . Here, it will trigger method of closeBox
GenericItem.vue
Changes on Template
<edit-category
v-if="editCategoryStatus"
:taskItem="taskItemLocal"
#close="closeBox($event)"
/>
Add one closeBox method
closeBox() {
this.editCategoryStatus = !this.editCategoryStatus;
},
Add editCategoryStatus on data
data() {
return {
editCategoryStatus: true,
taskItemLocal: {
type: Object,
default: {},
},
};
If you want to listen to an event within the component that emitted that event, you use the instance $on method:
mounted() {
this.$on("close", () => {
this.closeModal();
});
}
The template event handler #close="closeModal()" is used to listen to events from parent. It has no effect within the child component.
The working codesandbox: https://codesandbox.io/s/loving-kirch-vrhwn?file=/src/components/EditCategory.vue .
You could just make your button like this. You made this more complicated than it should be
<button class="modal-default-button" #click="showModal = false">
Also, there is this example from the official docs
here.

How to enable only active tab and disable all other tabs by default in Angularjs?

I have four tabs as "Tab1, Tab2, Tab3, Tab4".
By default all tabs should be disabled and active tab should be enabled.
If I click on submit button in active tab then I should automatically navigate to next tab by enabling the next tab and setting it as active and disabling the previous tab.
<li class="myli" ng-repeat="tab in tabs track by $index" ng-class="{active:isSelected($index)}"><a href ng-click="displaySelectedtab(tab, $index)">{{tab}}</a></li>
<div class="panel-body newPanelBody" ng-if="displaytab1 && !displaytab2 && !displaytab3 && !displaytab4">
<form name="actForm" role="form" data-ng-init="resp()" ng-submit="save()" novalidate>
<h4>Tab1</h4>
<br>
<button class="btn save sbmt" type="submit" id="submit">SAVE & CONTINUE</button>
</form>
</div>
<div class="panel-body newPanelBody" ng-if="displaytab2 && !displaytab1 && !displaytab3 && !displaytab4">
<form name="actForm" role="form" data-ng-init="resp()" ng-submit="save()" novalidate>
<h4>Tab2</h4>
<br>
<button class="btn save sbmt" type="submit" id="submit">SAVE & CONTINUE</button>
</form>
</div>
<div class="panel-body newPanelBody" ng-if="displaytab3 && !displaytab1 && !displaytab2 && !displaytab4">
<form name="actForm" role="form" data-ng-init="resp()" ng-submit="save()" novalidate>
<h4>Tab3</h4>
<br>
<button class="btn save sbmt" type="submit" id="submit">SAVE & CONTINUE</button>
</form>
</div>
<div class="panel-body newPanelBody" ng-if="displaytab4 && !displaytab1 && !displaytab2 && !displaytab3">
<form name="actForm" role="form" data-ng-init="resp()" ng-submit="save()" novalidate>
<h4>Tab4</h4>
<br>
<button class="btn save sbmt" type="submit" id="submit">SAVE & CONTINUE</button>
</form>
</div>
First off, your use of .panel-body and .btn has me assuming you use bootstrap, so have a look here: https://angular-ui.github.io/bootstrap/
There's a tabs component on that page made for use with angular and bootstrap.
Secondly, instead of using booleans to control which tab should show, it is much easier to use an integer to control the currently selected tab. That will also allow you to work with a variable amount of tabs.
<li class="myli" ng-repeat="tab in tabs track by $index" ng-class="{active: selectedIndex == $index}"><a href ng-click="displaySelectedtab(tab, $index)">{{tab}}</a></li>
<div class="panel-body newPanelBody" ng-repeat="tab in tabs track by $index" ng-if="selectedIndex == $index">
<h4>Tab {{$index + 1}}</h4>
<!-- If you need different content for each tab you can include an angular template as well -->
<ng-include src="'path/to/template.tpl.html'"></ng-include>
</div>
It will require you to think about how to store your tab content a little. The easiest way is probably to use templates. In that case you could devise a strategy where your tabs array contains objects that contain both the tab title as well as the content template url, like so:
$scope.tabs = [
{
"title": "Tab 1",
"templateUrl": "path/to/template.tpl.html"
}
];
Your ng-include would then look like like:
<ng-include src="tab.templateUrl"></ng-include>
Making your form's submit action go to a different tab then becomes a simple matter of changing the $scope.selectedIndex variable to the index of the tab you want opened.
Change ng-if="displaytab1 && !displaytab2 && !displaytab3 && !displaytab4" to ng-if="$index==selected"
In your button submit function add index like this ng-submit="save($index)"
In controller method:
`$scope.selected=1; $scope.save= function(index){selected=index+1;}`

Categories

Resources