I ended up fixing it. In the end I found it to be a livewire issue caused by an oversight by myself. I solved it by adding wire:key to the tabs root div element.
Nov 30, 2023
1
Level 1
Alpine Scope? issue.
Hey guys,
I'm having what I would assume is an alpinejs scoping issue with x-data.
I have a tab system like so:
<div class="flex-row mx-4 w-full">
@include($currentPricingTab)
</div>
and in my tabs, I have the following:
tab1
<div class="flex-row" x-data="labour">
<x-table>
<x-slot:heading>
<x-table.heading>Item</x-table.heading>
<x-table.heading>Trade</x-table.heading>
<x-table.heading>Type</x-table.heading>
<x-table.heading>Hours</x-table.heading>
<x-table.heading hidden>Meters</x-table.heading>
<x-table.heading>Rate</x-table.heading>
<x-table.heading>Total</x-table.heading>
<x-table.heading></x-table.heading>
</x-slot:heading>
<template x-for="(e, index) in items" :key="index">
<x-table.row x-on:click.outside="cancelEditingState(index)" x-on:dblclick="editRow(index)" x-on:keydown.enter="cancelEditingState(index)">
<x-table.cell class="w-4/12">
<div x-show="e.editing"><x-wire-input x-model='e.item.description'></x-wire-input></div>
<div x-text="e.item.description" x-show="!e.editing"></div>
</x-table.cell>
<x-table.cell class="w-32">
<div x-show="e.editing">
<x-wire-native-select :options="['ea', 'lm', 'm2', 'm3']" x-model="e.item.unit" class="!text-black"/>
</div>
<div x-text="e.item.unit" x-show="!e.editing"></div>
</x-table.cell>
<x-table.cell class="w-56">
<div x-show="e.editing">
<x-wire-inputs.currency placeholder="0.00" prefix="$" x-model="e.item.unit_price" x-on:input="calculateTotal()" />
</div>
<div x-show="!e.editing" class="inline-flex">$<div x-text="e.item.unit_price"></div></div>
</x-table.cell>
<x-table.cell class="w-32">
<div x-show="e.editing"><x-wire-input x-model='e.item.quantity' x-on:input="calculateTotal()"></x-wire-input></div>
<div x-text="e.item.quantity" x-show="!e.editing"></div>
</x-table.cell>
<x-table.cell class="w-56">
<div x-show="e.editing">
<x-wire-inputs.currency placeholder="0.00" prefix="$" x-model="e.item.total" />
</div>
<div x-show="!e.editing" class="inline-flex">$<div x-text="e.item.total"></div></div>
</x-table.cell>
<x-table.cell>
<div><x-wire-button x-on:click="deleteRow(index)">-</x-wire-button></div>
</x-table.cell>
</x-table.row>
</template>
<x-table.row @click.stop="">
<x-table.cell>
<x-wire-button primary class="rounded-full h-5" x-on:click="addRow">+</x-wire-button>
</x-table.cell>
</x-table.row>
</x-table>
</div>
@script
<script>
Alpine.data('labour', () => ({
'items': @entangle('labourItems'),
'currentlyEditing': @entangle('lCurrentlyEditing'),
addRow() {
console.log('cakked');
if (this.items.length > 0 && this.isItemEmpty(this.items[this.currentlyEditing].item)) {
return;
}
let newRowItem = {
'editing': true,
'item': {
'description': '',
'unit': '',
'unit_price': null,
'quantity': null,
'total': null,
},
};
this.items.push(newRowItem);
this.editRow(this.items.length - 1);
this.$dispatch('row-added');
},
changeEditStatus(index) {
if (this.currentlyEditing !== index) {
this.items[this.currentlyEditing].editing = false;
this.currentlyEditing = index;
}
},
isItemEmpty(item) {
for (let key in item) {
if (item.hasOwnProperty(key) && item[key]) {
return false;
}
}
return true;
},
cancelEditingState(index) {
this.recalculateTotalPrice();
if (this.isItemEmpty(this.items[index].item)) {
this.items.pop(this.currentlyEditing);
this.currentlyEditing--;
return;
}
if (this.currentlyEditing === index) {
this.items[this.currentlyEditing].editing = false;
}
},
editRow(index) {
this.recalculateTotalPrice();
if (this.items.length <= 0) {
return;
}
if (this.items[this.currentlyEditing]) {
this.items[this.currentlyEditing].editing = false;
}
if (this.items[index]) {
this.items[index].editing = true;
}
this.currentlyEditing = index;
},
deleteRow(index) {
this.recalculateTotalPrice();
if (this.items[index]) {
this.items.splice(index, 1);
if (this.items.length <= 0) {
this.currentlyEditing = 0;
return;
}
this.currentlyEditing--;
}
},
calculateTotal() {
if (this.items[this.currentlyEditing]) {
const total = this.items[this.currentlyEditing].item.unit_price * this.items[this.currentlyEditing].item.quantity;
this.items[this.currentlyEditing].item.total = total;
}
},
recalculateTotalPrice() {
this.$wire.call('recalculateTotalPrice');
}
}))
</script>
@endscript
tab2
<div class="flex-row" x-data="materials">
<x-table>
<x-slot:heading>
<x-table.heading>Item</x-table.heading>
<x-table.heading>Unit</x-table.heading>
<x-table.heading>Unit Price</x-table.heading>
<x-table.heading>Qty</x-table.heading>
<x-table.heading>Total</x-table.heading>
<x-table.heading></x-table.heading>
</x-slot:heading>
<template x-for="(ie, index) in items" :key="index">
<x-table.row x-on:click.outside="cancelEditingState(index)" x-on:dblclick="editRow(index)" x-on:keydown.enter="cancelEditingState(index)">
<x-table.cell class="w-4/12">
<div x-show="ie.editing"><x-wire-input x-model='ie.item.description'></x-wire-input></div>
<div x-text="ie.item.description" x-show="!ie.editing"></div>
</x-table.cell>
<x-table.cell class="w-32">
<div x-show="ie.editing">
<x-wire-native-select :options="['ea', 'lm', 'm2', 'm3']" x-model="ie.item.unit" class="!text-black"/>
</div>
<div x-text="ie.item.unit" x-show="!ie.editing"></div>
</x-table.cell>
<x-table.cell class="w-56">
<div x-show="ie.editing">
<x-wire-inputs.currency placeholder="0.00" prefix="$" x-model="ie.item.unit_price" x-on:input="calculateTotal()" />
</div>
<div x-show="!ie.editing" class="inline-flex">$<div x-text="ie.item.unit_price"></div></div>
</x-table.cell>
<x-table.cell class="w-32">
<div x-show="ie.editing"><x-wire-input x-model='ie.item.quantity' x-on:input="calculateTotal()"></x-wire-input></div>
<div x-text="ie.item.quantity" x-show="!ie.editing"></div>
</x-table.cell>
<x-table.cell class="w-56">
<div x-show="ie.editing">
<x-wire-inputs.currency placeholder="0.00" prefix="$" x-model="ie.item.total" />
</div>
<div x-show="!ie.editing" class="inline-flex">$<div x-text="ie.item.total"></div></div>
</x-table.cell>
<x-table.cell>
<div><x-wire-button x-on:click="deleteRow(index)">-</x-wire-button></div>
</x-table.cell>
</x-table.row>
</template>
<x-table.row @click.stop="">
<x-table.cell>
<x-wire-button primary class="rounded-full h-5" x-on:click="addRow">+</x-wire-button>
</x-table.cell>
</x-table.row>
</x-table>
</div>
@script
<script>
Alpine.data('materials', () => ({
'items': @entangle('materialItems'),
'currentlyEditing': @entangle('mCurrentlyEditing'),
addRow() {
if (this.items.length > 0 && this.isItemEmpty(this.items[this.currentlyEditing].item)) {
return;
}
let newRowItem = {
'editing': true,
'item': {
'description': '',
'unit': '',
'unit_price': null,
'quantity': null,
'total': null,
},
};
this.items.push(newRowItem);
this.editRow(this.items.length - 1);
this.$dispatch('row-added');
},
changeEditStatus(index) {
if (this.currentlyEditing !== index) {
this.items[this.currentlyEditing].editing = false;
this.currentlyEditing = index;
}
},
isItemEmpty(item) {
for (let key in item) {
if (item.hasOwnProperty(key) && item[key]) {
return false;
}
}
return true;
},
cancelEditingState(index) {
this.recalculateTotalPrice();
if (this.isItemEmpty(this.items[index].item)) {
this.items.pop(this.currentlyEditing);
this.currentlyEditing--;
return;
}
if (this.currentlyEditing === index) {
this.items[this.currentlyEditing].editing = false;
}
},
editRow(index) {
this.recalculateTotalPrice();
if (this.items.length <= 0) {
return;
}
if (this.items[this.currentlyEditing]) {
this.items[this.currentlyEditing].editing = false;
}
if (this.items[index]) {
this.items[index].editing = true;
}
this.currentlyEditing = index;
},
deleteRow(index) {
this.recalculateTotalPrice();
if (this.items[index]) {
this.items.splice(index, 1);
if (this.items.length <= 0) {
this.currentlyEditing = 0;
return;
}
this.currentlyEditing--;
}
},
calculateTotal() {
if (this.items[this.currentlyEditing]) {
const total = this.items[this.currentlyEditing].item.unit_price * this.items[this.currentlyEditing].item.quantity;
this.items[this.currentlyEditing].item.total = total;
}
},
recalculateTotalPrice() {
this.$wire.call('recalculateTotalPrice');
},
}))
</script>
@endscript
When the livewire component loads, and say tab1 is loaded by default, the tab 1 alpinejs functionality works as expected, where as if I go to tab2 from tab1, the alpinejs functionality doesn't work. An the vice versa happens if tab2 is loaded by default.
I know it has something to do with x-data, as the secondary tab (not the default loaded tab) is referencing the primary tabs 'items' array.
Any help would be appreciated +1
Please or to participate in this conversation.