How I can click on the element array? - javascript

I have two arrays, one - full elements(filterList), two - empty(addList). When I click on the first element (full array) it's added to second array (empty array). How I can click on the element (title:'DATA') ?
<ul class="flex">
<li class="text-white text-xs uppercase font-normal not-italic px-2 py-1.75 mr-3 border border-gray-900 border-solid rounded-xs transition duration-500 hover:cursor-pointer hover:border-purple hover:bg-black-700"
v-for="(item, index) of addList"
:item="item"
:key="index"
#click="testClick()"
>
<span class="flex items-center">{{item.title}} <IconCheckMark class="stroke-white ml-2.25"/></span>
</li>
</ul>
<ul class="border border-purple rounded-md p-2.5 inline-block min-w-35.25 bg-black-900 absolute z-110 max-h-64 overflow-y-auto overflow-x-hidden">
<li
v-for="(item, index) of filterList"
:item="item"
:key="index"
class="text-xs text-white uppercase py-1.5 px-2.5 hover:bg-black-700 hover:rounded-xs hover:cursor-pointer transition-all ease-in" #click="addFiltres(item)"
>
<span>{{item.title}}</span>
</li>
</ul>
addFiltres(item){
if (this.addList.indexOf(this.filterList) == this.addList.lastIndexOf(this.filterList)) {
this.addList.push(item)
}
this.addList = \[...new Set(this.addList)\]
},
testClick() {
},
filterList: \[
{title: 'DATE', value: 'date'},
{title: 'TIME', value: 'time'},
{title: 'COLOR', value: 'color'},
],
addList:[]
I try to solve this problem: 1) indexOf () 2) find() 3) includes(). Not work.

Related

Component vue3 can't initialaize array data on created

I have a component that it has v-for to showing multiple repetition of data, I use vue3 with composition API, When I initialize table of data and log it, it shows the right value what I want, but the length it is 0 and it's not rendring on template, I don't know why !!
template :
<template>
<base-page>
<form #submit.prevent="submitForm">
<base-header>
<template #title>
<nav class="py-2 rounded-md w-full">
<ol class="list-reset flex">
<li>
<router-link
to="/dashboard"
class="text-blue-600 hover:text-blue-700"
>Accueil</router-link
>
</li>
<li><span class="text-gray-500 mx-2">/</span></li>
<li class="text-gray-500">
Ajouter une réservation pour {{ terrain.name }}
</li>
</ol>
</nav>
</template>
</base-header>
<div class="mt-4">
<div class="flex text-center grid grid-cols-5">
{{sessionList}}
<div v-for="(item, index) in sessionList" :key="index" class="rounded-lg ml-2 shadow-lg bg-white max-w-sm">
<h5 class="text-gray-900 text-center font-medium m-2">Card title</h5>
<a href="#!">
<img class="rounded-t-lg m-auto w-16 h-16" src="/img/green-ball.png" alt=""/>
</a>
<div class="p-2">
<p class="text-gray-700 text-base mb-4">
Some quick example text to {{index}}
</p>
<button type="button" class=" inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out">
Réserver
</button>
</div>
</div>
</div>
</div>
</form>
</base-page>
</template>
script :
<script setup>
import {ref,onMounted} from "vue"
import {mdiContentSavePlusOutline} from "#mdi/js"
import moment from "moment"
moment.locale('fr');
const terrain = ref({
id: 1,
name: "terrain 1",
capacity: 11,
duration: 1,
price: 8,
external: true,
complexe: "Stade 1",
formatted_created_at: "10/10/2022",
})
let date = new moment()
let datesList = []
let sessionList = []
let nextDate = date
for (let index = 0; index < 15; index++) {
datesList.push(nextDate.format('YYYY-MM-DD'))
let tab = []
let hourDate = nextDate
for (let i = 0; i < 4; i++) {
let debut = moment(nextDate).add(i+1,'hours').format('HH:mm:ss')
let fin = moment(nextDate).add(i+2,'hours').format('HH:mm:ss')
tab.push({
debut : debut,
fin : fin,
reserved : i % 2 == 0
})
}
sessionList[datesList[index]] = tab
nextDate = date.add(1,'days');
}
console.log(datesList)
console.log(sessionList)
</script>
log :
Edit
the sessionList variable it would have like this format :
You define sessionList as an array, but you are adding items to it like an object. So the arrays length is still 0, but you added properties with the dates as you can see in the screenshot. This is also why nothing is rendered.
I am not sure what you want to do with the date, but maybe just add it as another property of the tab object and then push this to the array:
tab.push({
debut : debut,
fin : fin,
reserved : i % 2 == 0,
date: datesList[index]
});
sessionList.push(tab);
EDIT after updated question:
Define sessionList as a Map:
const sessionList = new Map();
Then adding entries:
sessionList.set(datesList[index], tab);
In the template you then have to replace the v-for with:
<div v-for="[key, value] in sessionList" :key="key" ...>

State Update prevents classList.toggle()

I am making a timeslot-picker component and want to toggle the class of each individual timeslot-item so it changes color and also add the timeslot information to an array of objects. If I update the state array with an onClick function the classList.toggle function doesn't seem to work, as none of the timeslots change color. I would appreciate any help on how to combine both functionalites. This is the part of my component required for the functionality:
<ul className="w-[180px] h-[400px] overflow-y-scroll flex flex-col gap-1 pt-4 relative">
<div className="text-center ">{day.day}</div>
{timeslots.map((timeslot) => (
<li key={Math.random()} className="mt-3">
<button
onClick={(e) => {
e.currentTarget.classList.toggle('bg-violet-500');
setRequestedTimeslots([
...requestedTimeslots,
{
day: day.day,
start: timeslot.start,
end: timeslot.end,
},
]);
}}
className="px-4 py-2 bg-[#F5F5F5] rounded-xl w-full text-center"
>
{timeslot.start} - {timeslot.end}
</button>
</li>
))}
</ul>

Use useVirtualList from VueUse to render HeadlessUI's Listbox component

I want to use VueUse's virtual scroller composable to render the HeadlessUI's listbox content.
This is what I tried:
<Listbox v-model="selectedIcon">
<div class="relative mt-1">
<ListboxButton class="bg-white rounded-lg cursor-default shadow-md text-left w-full py-2 pr-10 pl-3 relative sm:text-sm focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-white focus-visible:ring-2 focus-visible:ring-opacity-75 focus-visible:ring-offset-orange-300 focus-visible:ring-offset-2">
<span class="block truncate">test</span>
<span class="flex pr-2 inset-y-0 right-0 absolute items-center pointer-events-none">
<SelectorIcon class="h-5 text-gray-400 w-5" aria-hidden="true" />
</span>
</ListboxButton>
<ListboxOptions v-bind="containerProps" class="bg-white rounded-md shadow-lg ring-black mt-1 text-base w-full max-h-60 h-full py-1 ring-1 ring-opacity-5 absolute overflow-auto sm:text-sm focus:outline-none">
<div v-bind="wrapperProps">
<ListboxOption v-slot="{ active, selected }" v-for="icon in list" :key="icon.data.name" :value="icon">
<li>
<span>{{ icon.data.name }}</span>
</li>
</ListboxOption>
</div>
</ListboxOptions>
</div>
</Listbox>
And this is the virtual scroller composable:
const { list, containerProps, wrapperProps } = useVirtualList(icons, { itemHeight: 40 })
The problem is that when I try to open the listbox, I get this error:
Uncaught (in promise) TypeError: Failed to execute 'observe' on 'ResizeObserver': parameter 1 is not of type 'Element'.
at watch.immediate (index.mjs:1322)
at callWithErrorHandling (runtime-core.esm-bundler.js:6737)
at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:6746)
at Array.job (runtime-core.esm-bundler.js:7154)
at flushPostFlushCbs (runtime-core.esm-bundler.js:6938)
at flushJobs (runtime-core.esm-bundler.js:6983)
at flushJobs (runtime-core.esm-bundler.js:6991)
I also get this warning:
[Vue warn]: Unhandled error during execution of watcher callback
at <ApplicationIconSelector key=0 >
at <Anonymous as="template" enter="ease-out duration-300" enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" ... >
at <ForcePortalRoot force=false >
at <PortalGroup target= <div class=​"fixed z-10 inset-0 overflow-y-auto" id=​"headlessui-dialog-4" role=​"dialog" aria-modal=​"true" aria-labelledby=​"headlessui-dialog-title-8">​…​</div>​<div class=​"flex min-h-screen text-center px-4 pt-4 pb-20 items-end justify-center sm:​p-0 sm:​block">​<div id=​"headlessui-dialog-overlay-6" aria-hidden=​"true" class=​"bg-gray-975 bg-opacity-85 inset-0 transition-opacity fixed">​</div>​<!-- This element is to trick the browser into centering the modal contents. --><span class=​"hidden sm:​h-screen sm:​inline-block sm:​align-middle" aria-hidden=​"true">​&ZeroWidthSpace;​</span>​<div class=​"rounded-lg shadow-xl text-left transform transition-all text-gray-850 inline-block align-bottom sm:​max-w-lg sm:​my-8 sm:​w-full sm:​align-middle dark:​text-gray-200">​<div class=​"rounded-t-lg bg-gray-25 px-4 pt-5 pb-4 sm:​p-6 sm:​pb-4 dark:​bg-gray-925">​…​</div>​<div class=​"bg-gray-75 py-3 px-3 sm:​flex sm:​flex-row-reverse dark:​bg-gray-900">​…​</div>​flex<button class=​"flex-y-center justify-center p-2 px-3 font-medium text-sm transition duration-75 select-none cursor-pointer focus:​outline-none rounded-md bg-gray-75 text-gray-850 hover:​bg-gray-100 active:​bg-gray-175 dark:​text-gray-250 dark:​bg-gray-875 dark:​hover:​bg-gray-850 dark:​active:​bg-gray-825 w-full">​…​</button>​flex</div>​</div>​</div>​</div>​ >
at <Portal>
at <ForcePortalRoot force=true >
at <Dialog as="div" class="fixed z-10 inset-0 overflow-y-auto" ref="el" ... >
at <Anonymous onBeforeEnter=fn<onBeforeEnter> onAfterEnter=fn<onAfterEnter> onBeforeLeave=fn<onBeforeLeave> ... >
at <Anonymous as="template" show=true data=null >
at <AddEditApplication modelValue=true onUpdate:modelValue=fn data=null >
at <Index onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< Proxy {__v_skip: true} > >
at <RouterView>
at <App>
Uncaught (in promise) TypeError: Failed to execute 'observe' on 'ResizeObserver': parameter 1 is not of type 'Element'.
Looks like it is attempting to bind itself to something other than a native DOM element. In the example on the VueUse site they show native DOM elements with the v-bind directive. However, your code uses v-bind on non-native DOM elements, you have it on a VNode (the ListBox component).
Even looking at the source code you can see that the binding of containerProps expects an HTMLElement.
const containerRef: Ref = ref<HTMLElement | null>()
I've not used HeadlessUI before, but looking at the source code for the ListBoxOptions component it appears that it does not render any elements outside of what is passed into its default slot; aka your <div v-bind="wrapperProps">. Is your list of classes even rendering on anything? Hard to tell how your code is running without an example.
I recommend creating another div, nested inside of ListBoxOptions, that wraps the <div v-bind="wrapperProps"> element. On this new div move the v-bind="containerProps" from the ListBoxOptions onto it; see below.
<ListboxOptions>
<div v-bind="containerProps" class="bg-white rounded-md shadow-lg ring-black mt-1 text-base w-full max-h-60 h-full py-1 ring-1 ring-opacity-5 absolute overflow-auto sm:text-sm focus:outline-none">
<div v-bind="wrapperProps">
<ListboxOption v-slot="{ active, selected }" v-for="icon in list" :key="icon.data.name" :value="icon">
<li>
<span>{{ icon.data.name }}</span>
</li>
</ListboxOption>
</div>
</div>
</ListboxOptions>
I think that may fix your issue.

Vue - Disable button when item is added to cart. Enable when it's removed

I'm trying to add functionality to my addToCart button located in ProductCard. I need it to be disabled once the cupcake has been added to my cart array. When removed via the removeItem/ clearCart in MiniCart I need the button to be enabled again. I've tried if else statements and tried adding all kinds of functionality that I've Google's but have yet to succeed. I'd really appreciate some help ^^
store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
cart: [],
cupcake: [{
title: 'Cream',
price: 12.99,
image: 'https://images.pexels.com/photos/1055270/pexels-photo-1055270.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260',
id: 1,
quantity: 1
},
{
title: 'Choc',
price: 10.99,
image: 'https://images.pexels.com/photos/1055272/pexels-photo-1055272.jpeg?auto=compress&cs=tinysrgb&h=750&w=1260',
id: 2,
quantity: 1
},
{
title: 'Frosting',
price: 14.99,
image: 'https://images.pexels.com/photos/1055271/pexels-photo-1055271.jpeg?auto=compress&cs=tinysrgb&h=750&w=1260',
id: 3,
quantity: 1
},
{
title: 'Berry',
price: 9.99,
image: 'https://images.pexels.com/photos/3081657/pexels-photo-3081657.jpeg?auto=compress&cs=tinysrgb&h=750&w=1260',
id: 4,
quantity: 1
},
{
title: 'Deluxe',
price: 19.99,
image: 'https://images.pexels.com/photos/1998634/pexels-photo-1998634.jpeg?auto=compress&cs=tinysrgb&h=750&w=1260',
id: 5,
quantity: 1
},
{
title: 'Oreo',
price: 19.99,
image: 'https://images.pexels.com/photos/783274/pexels-photo-783274.jpeg?auto=compress&cs=tinysrgb&h=750&w=1260',
id: 6,
quantity: 1
},
]
},
});
ProductCard.vue
<template>
<ul
class="
px-32
py-16
grid
row-auto
gap-10
grid-cols-1
md:grid-cols-2
xl:grid-cols-3
2xl:grid-cols-4
"
>
<li
class="bg-white rounded-lg"
v-for="cupcakes in cupcake"
:key="cupcakes.id"
>
<img
class="rounded-md w-screen object-cover max-h-60"
:src="cupcakes.image"
/>
<div class="py-2 px-8 text-gray-600">
<span class="px-10 text-xl font-bold"> {{ cupcakes.title }}</span>
<span class="px-10 text-xl font-bold">${{ cupcakes.price }}</span>
<button
class="
bg-purple-200
font-bold
px-3
mt-2
text-xl
py-4
mb-4
w-full
rounded-md
transition-all
hover:bg-purple-300
"
type="button"
v-on:click="addToCart(cupcakes)"
>
Add to Cart
</button>
</div>
</li>
</ul>
</template>
<script>
export default {
computed: {
cupcake() {
return this.$store.state.cupcake;
},
},
methods: {
addToCart(cupcakes) {
this.$store.state.cart.push({
title: cupcakes.title,
price: cupcakes.price,
image: cupcakes.image,
id: cupcakes.id,
quantity: cupcakes.quantity,
});
},
},
};
</script>
Minicart.vue
<template>
<div class="bg-white border-2 border-gray-500 rounded-md absolute right-16">
<div
class="grid grid-cols-2 gap-20 m-5"
v-for="carts in cart"
:key="carts.id"
>
<img class="w-24" :src="carts.image" alt="" />
<div class="grid grid-rows-3">
<strong class="tracking-wider font-bold">{{ carts.title }}</strong>
<p class="tracking-wider font-bold">
{{ carts.quantity }} x ${{ carts.price }}
</p>
<button
class="bg-gray-500 rounded p-2 tracking-wider font-bold text-white"
v-on:click="removeItem(carts)"
>
remove
</button>
<button type="button" v-on:click="increase(carts)">Increase</button>
<button type="button" v-on:click="deccrease(carts)">Deccrease</button>
</div>
</div>
<div class="flex mx-5 my-8 justify-between">
<span
class="tracking-wider text-xl p-4 font-bold justify-center align-middle"
>Total: ${{ total }}</span
>
<button
v-on:click="clearCart"
type="button"
href=""
class="
bg-red-400
p-4
rounded
tracking-wider
text-white text-xl
font-bold
"
>
Clear Cart
</button>
</div>
</div>
</template>
<script>
export default {
computed: {
cart() {
return this.$store.state.cart;
},
total: function () {
let total = 0;
for (let carts of this.$store.state.cart) {
total += carts.price * carts.quantity;
}
return total.toFixed(2);
},
},
methods: {
removeItem: function (carts) {
this.$store.state.cart.splice(carts, 1);
},
increase: function (carts) {
carts.quantity += 1;
},
deccrease: function (carts) {
if (carts.quantity > 1) {
carts.quantity -= 1;
} else {
this.$store.state.cart.splice(carts, 1);
}
},
clearCart: function () {
let length = this.$store.state.cart.length;
this.$store.state.cart.splice(0, length);
console.log(length);
},
},
};
</script>
Just like that. Demo https://codesandbox.io/s/jolly-shirley-dgg10
Template:
<button
class="bg-purple-200 font-bold px-3 mt-2 text-xl py-4 mb-4 w-full
rounded-md transition-all hover:bg-purple-300"
type="button"
v-on:click="addToCart(cupcakes)"
:disabled="checkCart(cupcakes.id)"
>
Add to Cart
</button>
Methods:
checkCart(id) {
return this.$store.state.cart.find((item) => item.id === id);
},
If you add this disabled-method to your button, it will be disabled, if the cupcake is already in the cart, and you can still add the other cupcakes:
<button
class="
...
"
type="button"
v-on:click="addToCart(cupcakes)"
:disabled="{ cart.includes(cupcakes) }"
>
Add to Cart
</button>
Your AddToCart-function could also be rewritten to:
addToCart(cupcakes) {
this.$store.state.cart.push(cupcakes);
},

alpine.js table edit-in-place functionality

I'm trying to make a table column editable inline using Alpine.js. The idea is to have an "edit in place" functionality so when a row is double-clicked allows for the content to be editable. The issue I'm having is when a cell is clicked it activates all rows.
The ideal behavior is only the clicked row should be editable, all others should remain uneditable.
I have a preview of the issue here, https://codepen.io/ezeagwulae/pen/ZEKeYGQ
<div x-data="data()" class="p-4">
<div class="uppercase font-bold">shopping items</div>
<template x-for="item in items">
<div>
<a #click.prevent #dblclick="toggleEditingState" x-show="!isEditing" x-text="item.item" class="select-none cursor-pointer underline font-lg text-blue-500"></a>
<input type="text" x-model="text" x-show="isEditing" #click.away="toggleEditingState" #keydown.enter="disableEditing" #keydown.window.escape="disableEditing" class="bg-white focus:outline-none focus:shadow-outline border border-gray-300 rounded-lg py-2 px-4 appearance-none leading-normal w-128" x-ref="input">
</div>
</template>
</div>
In your JS file make sure to get the double-clicked input field with e.target.
In your HTML x-model should be set to item.item.
Here's a working example.
HTML
<div x-data="data()" class="p-4">
<div class="uppercase font-bold">shopping items</div>
<template x-for="item in items">
<div>
<a #click.prevent #dblclick="toggleEditingState" x-show="!isEditing" x-text="item.item" class="select-none cursor-pointer underline font-lg text-blue-500"></a>
<input type="text" x-model="item.item" x-show="isEditing" #click.away="toggleEditingState" #keydown.enter="disableEditing" #keydown.window.escape="disableEditing" class="bg-white focus:outline-none focus:shadow-outline border border-gray-300 rounded-lg py-2 px-4 appearance-none leading-normal w-128" x-ref="input">
</div>
</template>
</div>
JS
function data() {
return {
text: "Double click to edit",
isEditing: false,
toggleEditingState(e) {
const el = e.target
this.isEditing = !this.isEditing;
el.focus()
},
disableEditing() {
this.isEditing = false;
},
items: [
{ id: 1, item: "apple" },
{ id: 2, item: "eggs" },
{ id: 3, item: "milk" }
]
};
}
Any suggestions to make only the clicked row editable and not all rows? For instance, if "eggs" the input field should be shown for this row and the other rows should remain as is
For example like this:
<link href="https://unpkg.com/tailwindcss#^2/dist/tailwind.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine#v2.x.x/dist/alpine.js"></script>
<div
x-data="{
items: [
{ id: 1, item: 'apple', edit: false },
{ id: 2, item: 'eggs', edit: false },
{ id: 3, item: 'milk', edit: false },
]
}"
class="p-4"
>
<div class="uppercase font-bold">shopping items</div>
<template x-for="(item, index) in items">
<div>
<a
#click.prevent
#dblclick="
item.edit = true;
$nextTick(() => $refs[item.id].focus());
"
#click.away="item.edit = false"
x-show="!item.edit"
x-text="item.item"
class="
select-none
cursor-pointer
underline
font-lg
text-blue-500
"
></a>
<input
type="text"
x-model="item.item"
x-show="item.edit"
#click.away="item.edit = false"
#keydown.enter="item.edit = false"
#keydown.window.escape="item.edit = false"
class="
bg-white
focus:outline-none focus:shadow-outline
border border-gray-300
rounded-lg
py-2
px-4
appearance-none
leading-normal
w-128
"
:x-ref="item.id"
/>
</div>
</template>
</div>

Categories

Resources