Vue JS click event not firing up - vue.js

I'm developing app where I have a dynamic .ddlist for each .box.
<div id="app">
<h2>MY LIST</h2>
<div class="box">
<input v-on:focus="showDDList" v-on:blur="showDDList" type="text" value="" placeholder="hello list">
<ul class="ddlist">
<li #click.prevent="myalert()" v-for="item in hello">{{item.text}}</li>
</ul>
</div>
<div class="box">
<input v-on:focus="showDDList" v-on:blur="showDDList" type="text" value="" placeholder="there list">
<ul class="ddlist">
<li #click.prevent="myalert()" v-for="item in there">{{item.text}}</li>
</ul>
</div>
</div>
What I want to achieve is when user focus on input, very next .ddlist should be visible. Here is Javascript part :
new Vue({
el: "#app",
data: {
hello: [
{ text: "hello 1" },
{ text: "hello 2" },
{ text: "hello 3" },
{ text: "hello 4" }
],
there: [
{ text: "there 1" },
{ text: "there 2" },
{ text: "there 3" },
{ text: "there 4" }
]
},
methods: {
showDDList: function(e) {
e.target.nextElementSibling.classList.toggle('active');
},
myalert: function() {
alert("Hello world");
},
}
})
My code is working fine until here and .ddlist does show up on focusing on input
What Next I want to do is When a user clicks on .ddlist child a, It should fire #click event binded to it.
There is a problem. Now when user clicks on a nothing shows up.
What I notice is because I'm toggling with css display:block/none of .ddlist, #click event is not firing up.
Here is the working fiddle : https://jsfiddle.net/6y9mwh3u/
May be you can throw some light on achieving my result with alternative ?

You can use mousedown event listener instead in order to execute myalert before showDDList since blur was preventing click from being called.
new Vue({
el: "#app",
data: {
hello: [
{ text: "hello 1" },
{ text: "hello 2" },
{ text: "hello 3" },
{ text: "hello 4" }
],
there: [
{ text: "there 1" },
{ text: "there 2" },
{ text: "there 3" },
{ text: "there 4" }
]
},
methods: {
showDDList: function(e) {
e.target.nextElementSibling.classList.toggle('active');
},
myalert: function() {
alert("Hello world");
},
}
})
.box {
display: block;
padding: 10px;
margin: 10px;
border: 1px #ddd solid;
}
.ddlist {
display: none;
margin-top : 10px;
}
.ddlist.active {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>MY LIST</h2>
<div class="box">
<input v-on:focus="showDDList" v-on:blur="showDDList" type="text" value="" placeholder="hello list">
<ul class="ddlist">
<li #mousedown.prevent="myalert" v-for="item in hello">{{item.text}}</li>
</ul>
</div>
<div class="box">
<input v-on:focus="showDDList" v-on:blur="showDDList" type="text" value="" placeholder="there list">
<ul class="ddlist">
<li #mousedown.prevent="myalert" v-for="item in there">{{item.text}}</li>
</ul>
</div>
</div>

Related

Why does my class not show in vue but my conditional class does?

I want to show two classes in my button. One that is conditional on a Boolean data value like "hideLeftArrow" and another class that shows up as the default like arrowBtn. The conditional class shows but the default class doesn't. What is wrong with my syntax in the following:
:class="[{ hideArrow: hideLeftArrow }, arrowBtn]"
My full code for reference
<template>
<div class="showcase-container">
<button
#click="showRightArrow"
:class="[{ hideArrow: hideLeftArrow }, arrowBtn]"
>
<img alt="left arrow" src="../assets/leftArrow.svg" class="arrow" />
</button>
<a
v-for="game in games"
:key="game.id"
:class="{ hideGame: game.hide }"
:href="game.link"
target="_blank"
>{{ game.name }}</a
>
<button
#click="showLeftArrow"
:class="[{ hideArrow: hideRightArrow }, arrowBtn]"
>
<img alt="right arrow" src="../assets/rightArrow.svg" class="arrow" />
</button>
</div>
</template>
<script>
export default {
data() {
return {
games: [
{
name: "Tryangle",
link: "https://google.com",
id: 1,
hide: false,
},
{
name: "BagRPG",
link: "https://youtube.com",
id: 2,
hide: true,
},
],
hideLeftArrow: true,
hideRightArrow: false,
};
},
methods: {
showLeftArrow() {
this.hideLeftArrow = !this.hideLeftArrow;
this.hideRightArrow = !this.hideRightArrow;
this.games[0].hide = true;
this.games[1].hide = false;
},
showRightArrow() {
this.hideLeftArrow = !this.hideLeftArrow;
this.hideRightArrow = !this.hideRightArrow;
this.games[0].hide = false;
this.games[1].hide = true;
},
},
};
</script>
<style lang="scss">
#import "../styles.scss";
.showcase-container {
.hideArrow {
visibility: hidden;
}
.arrowBtn {
background: none;
border: 0;
}
.hideGame {
display: none;
}
}
</style>
I think you're aiming for this, with quotes around arrowBtn:
:class="[{ hideArrow: hideLeftArrow }, 'arrowBtn']"
Otherwise arrowBtn will be treated as a property name and not a string..
That said, I'd probably do it this way instead:
class="arrowBtn"
:class="{ hideArrow: hideLeftArrow }"
class allows you to have both a static and a bound version on the same element.

Vue draggable works for whole group but not for each item

The code below allows dragging the groups in toListFetchRouteA1:
<draggable id="first" data-source="juju" :list="toListFetchRouteA1" class="list-group" draggable=".item" group="a">
<div class="list-group-item item" v-for="teamfetch in toListFetchRouteA1" :key="teamfetch.id">
<div v-for="personame in teamfetch.Team_player_sessions" :key="personame.id">
{{personame.Player.Person.first_name}}
</div>
</div>
</draggable>
I am trying to allow dragging each person name rather than by group, so I changed :list="toListFetchRouteA1" into :list="teamfetch.Team_player_sessions" (shown below), but it throws an error: Cannot read property 'Team_player_sessions' of undefined".
<draggable id="first" data-source="juju" :list="teamfetch.Team_player_sessions" class="list-group" draggable=".item" group="a">
<div v-for="teamfetch in toListFetchRouteA1" :key="teamfetch.id">
<div class="list-group-item item" v-for="personame in teamfetch.Team_player_sessions" :key="personame.id">
{{personame.Player.Person.first_name}}
</div>
</div>
</draggable>
It does not drag as well. Is there a way to make div v-for="personame in teamfetch.Team_player_sessions draggable for personname?
The error occurs because teamfetch is out of scope. It was declared on the inner v-for but used in the outer element.
You could make the person names draggable by wrapping them in a second draggable (assuming you want the groups themselves and the names within the groups to be draggable):
<draggable :list="toListFetchRouteA1" class="list-group" draggable=".item" group="a">
<div class="list-group-item item" v-for="teamfetch in toListFetchRouteA1" :key="teamfetch.id">
<!-- 2nd draggable for persons -->
<draggable :list="teamfetch.Team_player_sessions" draggable=".person">
<div v-for="personame in teamfetch.Team_player_sessions" :key="personame.id" class="person">
{{personame.Player.Person.first_name}}
</div>
</draggable>
</div>
</draggable>
new Vue({
el: '#app',
data: () => ({
toListFetchRouteA1: [
{
id: 1,
Team_player_sessions: [
{
id: 100,
Player: {
Person: {
first_name: 'john'
}
}
}
]
},
{
id: 2,
Team_player_sessions: [
{
id: 200,
Player: {
Person: {
first_name: 'adam'
}
}
},
{
id: 201,
Player: {
Person: {
first_name: 'allen'
}
}
}
]
},
{
id: 3,
Team_player_sessions: [
{
id: 300,
Player: {
Person: {
first_name: 'dave'
}
}
},
{
id: 301,
Player: {
Person: {
first_name: 'dylan'
}
}
}
]
},
]
}),
})
.item {
background: #eee;
margin: 2px;
padding: 10px;
}
.person {
background: #ccc;
margin: 2px;
}
<script src="https://unpkg.com/vue#2.6.11"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/sortablejs#1.8.4/Sortable.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/2.20.0/vuedraggable.umd.min.js"></script>
<div id="app">
<draggable :list="toListFetchRouteA1" class="list-group" draggable=".item" group="a">
<div class="list-group-item item" v-for="teamfetch in toListFetchRouteA1" :key="teamfetch.id">
<draggable :list="teamfetch.Team_player_sessions" draggable=".person">
<div v-for="personame in teamfetch.Team_player_sessions" :key="personame.id" class="person">
{{personame.Player.Person.first_name}}
</div>
</draggable>
</div>
</draggable>
<pre>{{toListFetchRouteA1}}</pre>
</div>

Custom radio component in latest Vue would not function at all in latest Safari

UPDATE: It is an issue of not being able to render the active class. Click does log data .
Expected
Current
RadioInput
<RadioInput
v-model="form.userType"
label="User Type"
:options="[
{ id: 'two', name: 'Investor', checked: true },
{ id: 'one', name: 'Entrepreneur', checked: false }
]"
/>
<template>
<ValidationProvider
class="mb-4 RadioInput"
tag="div"
data-toggle="buttons"
:rules="rules"
:name="name || label"
v-slot="{ errors, required, ariaInput, ariaMsg }"
>
<label
class="form-label"
:for="name || label"
#click="$refs.input.focus()"
:class="{ 'text-gray-700': !errors[0], 'text-red-600': errors[0] }"
>
<span>{{ label || name }} </span>
<small>{{ required ? " *" : "" }}</small>
</label>
<div class="btn-group btn-block btn-group-toggle" data-toggle="buttons">
<label
:for="item.id"
v-for="item in options"
class="btn btn-light"
:class="{ 'active': item.checked }"
>
<input
#click="update(item.name)"
type="radio"
:name="name || label"
class="form-control custom-control-input"
:id="item.id"
ref="input"
v-bind="ariaInput"
autocomplete="off"
:checked="item.checked"
/>
{{ item.name }}
</label>
</div>
<span
:class="{ 'invalid-feedback': errors[0] }"
v-bind="ariaMsg"
v-if="errors[0]"
>{{ errors[0] }}</span
>
</ValidationProvider>
</template>
<script>
import { ValidationProvider } from "vee-validate";
export default {
name: "RadioInput",
components: {
ValidationProvider
},
props: {
name: {
type: String,
default: ""
},
label: {
type: String,
default: ""
},
rules: {
type: [Object, String],
default: ""
},
options: {
type: Array,
default: []
}
},
methods: {
update(value) {
this.$emit("input", value);
}
},
created() {
this.$emit(
"input",
_.filter(this.options, { checked: true })[0]["name"]
);
}
};
</script>
<style scoped>
.invalid-feedback {
display: block;
}
</style>
const Wrapper = Vue.component("Wrapper", {
props: ["options", "activeIndex"],
template: `<div>
<div v-for="(item, index) in options" class="item" :class="{active: index === activeIndex}" #click="$emit('input', index)">{{item.name}}</div>
</div>`,
});
new Vue({
el: "#app",
template: `<div>
<Wrapper :options="options" :activeIndex="activeIndex" v-model="activeIndex"></Wrapper>
</div>`,
data() {
return {
options: [
{ id: "two", name: "Investor", checked: true },
{ id: "one", name: "Entrepreneur", checked: false }
],
activeIndex: -1
};
},
created() {
this.activeIndex = this.options.findIndex(i => i.checked);
}
});
body{
display: flex;
justify-content: center;
align-items: center;
}
.item{
width: 100px;
border: 1px solid;
padding: 10px;
}
.item.active{
background-color: rgba(0,0,0, 0.3);
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.0"></script>
<div id="app"></div>

How to make the fields independent?

I'm trying to make the fields/buttons independent of one another. Just like in the case of Adding more people. Example image
No matter how many fields I add, they'll not be linked to each other. I can remove/edit one of them and it'll only affect that field.
but I'm not sure how can I achieve similar behavior If I need to repeat same fields. If I click on Add Other City, a new city block would be created but with the previous data. Example image.
HTML:
<div id="app">
<div v-model="cities" class="city">
<form action="" v-for="(city, index) in cities">
<div class="column" v-for="(profile, index) in profiles">
<input type='text' v-model="profile.name" placeholder="Name">
<input type='text' v-model="profile.address" placeholder="Address">
<button type="button" #click="removeProfile">-</button>
</div>
<center><button type="button" #click="addProfile">Add More People</button></center>
<br><br><br>
</form>
<center>
<button type="button" #click="addCity">Add Other City</button>
<button type="button" #click="removeCity">Remove City</button>
</center>
</div>
</div>
JS:
new Vue({
el: '#app',
data: {
cities: [
{ name: '' }
],
profiles: [
{ name: '', address: '' }
],
},
methods: {
addProfile() {
this.profiles.push({
name: '',
address: ''
})
},
removeProfile(index) {
this.profiles.splice(index, 1);
},
addCity() {
this.cities.push({
// WHAT TO DO HERE?
})
},
removeCity(index) {
this.cities.splice(index, 1);
},
}
})
Here's a link to Jsfiddle: https://jsfiddle.net/91m8cf5q/4/
I first tried to do this.profiles.push({'name': '', 'address': ''}) inside this.cities.push({}) in addCity() but It's not possible (gives error).
What should I do to make them separate so that when I click Add Other City, new blank field would appear and removing fields from that city should not remove fields from the previous cities.
You should have profiles assigned to individual city
new Vue({
el: '#app',
data: {
cities: [],
},
mounted(){
this.cities.push({
name: '',
profiles: [
{
name: '',
address: '' }
]
});
},
methods: {
addProfile(index) {
this.cities[index].profiles.push({
name: '',
address: ''
})
},
removeProfile(index) {
this.profiles.splice(index, 1);
},
addCity() {
this.cities.push({
name: '',
profiles: [
{
name: '',
address: '' }
]
})
},
removeCity(index) {
this.cities.splice(index, 1);
},
}
})
#app form {
margin: 5px;
width: 260px;
border: 1px solid green;
padding-top: 20px;
}
.column {
padding: 5px;
}
.city {
width: 280px;
border: 1px solid red;
margin: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="city">
<form action="" v-for="(city, index) in cities">
<div class="column" v-for="(profile, index) in city.profiles">
<input type='text' v-model="profile.name" placeholder="Name">
<input type='text' v-model="profile.address" placeholder="Address">
<button type="button" #click="removeProfile">-</button>
</div>
<center><button type="button" #click="addProfile(index)">Add More People</button></center>
<br><br><br>
</form>
<center>
<button type="button" #click="addCity">Add Other City</button>
<button type="button" #click="removeCity">Remove City</button>
</center>
</div>
</div>
Profiles should be part of cities, like this
new Vue({
el: '#app',
data: {
cities: [{
name: '',
profiles: [
{ name: '', address: '' }
]}
]
},
methods: {
addProfile(city) {
city.profiles.push({
name: '',
address: ''
})
},
addCity() {
this.cities.push(
{ name: '',
profiles: [
{ name: '', address: '' }
]}
)
},
removeCity(index) {
this.cities.splice(index, 1);
},
}
})
HTML:
<div id="app">
<div class="city">
<form v-for="city in cities">
<div class="column" v-for="profile in city.profiles">
<input type='text' v-model="profile.name" placeholder="Name">
<input type='text' v-model="profile.address" placeholder="Address">
<button type="button" #click="removeProfile">-</button>
</div>
<center>
<button type="button" #click="addProfile(city)">Add More People</button>
</center>
<br><br><br>
</form>
<center>
<button type="button" #click="addCity">Add Other City</button>
<button type="button" #click="removeCity">Remove City</button>
</center>
</div>
<div>

How can I toggle one state only and undo it when I toggle the next (VueJS)

First, I apologize for the way I phrased the question. I couldn't find a way to word it in one sentence. So, allow me to explain.
I have an API with thousands of items being looped in a v-for. The user will select one and only one at a time from the collection. And upon clicking on it will show on a separate place along with some other data (* so it's not a case of string interpolation. It's an object. I'm just simplifying the code in my jsfiddle to make it less confusing and run without dependencies)
I added a boolean property to the API to toggle it true/false and a method function that does the toggle. And with the help of some v-if and v-show directives I'm hiding the other elements from rendering when they are false.
<div id="app">
<div uk-grid class="card-body">
<div class="uk-width-1-4#m">
<div>1. Select an Item</div>
<div class="pods" v-for="pod in pods" :key="pod.id" :id="pod.id">
<div class="ws-in-col" v-for="workstations in pod.workstations" :key="workstations.id" :id="workstations.id" #click="selectWS(workstations, pod)">
<div class="ws ws-number uk-text-default">{{workstations.number}}</div>
</div>
</div>
</div>
<div class="uk-width-1-4#m">
<div>2. Selected Item</div>
<div class="pods" v-for="pod in pods" :key="pod.id" :id="pod.id" v-show="pod.selected===true">
<div class="ws-in-col" v-for="workstations in pod.workstations" :key="workstations.id" :id="workstations.id" v-show="workstations.selected===true">
<div v-if="workstations.selected === true">
<div class="group">
<div class="ws ws-number uk-text-default">{{workstations.number}}</div>
<div class="ws ws-number uk-text-default">{{workstations.text}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
and in the script
methods: {
selectWS(workstations, pods) {
pods.selected = !pods.selected;
workstations.selected = !workstations.selected;
}
}
However, not only it is very messy and rookie. It's buggy. The only way it works is if the user clicks on one item to show it, and clicks it again to toggle it off before clicking another one to turn it on. That's far from user-friendly.
How can I resolve this in a cleaner and professional way so that if the user clicks on 1.1, it shows 1.1 and if he wants to see 1.2, all he has to do is click on 1.2 without having to turn off 1.1 first?
Here's a JSFIDDLE replicating my problem
Thanks guys. Being the only Vue dev in this place is tough.
Agree with IVO, instead of tracking in-item, use a value(or two) to track selection
The only reason I'm posting separate answer is that I'd recommend using a computed value if you need to have the selected instance available as an object.
new Vue({
el: "#app",
data: {
workstation: null,
pod: null,
pods: [
{
id: "pod1",
number: 1,
selected: false,
"workstations": [
{
id: "ws11",
number: "1.1",
text: "Some text",
selected: false,
close: false
},
{
id: "ws12",
number: "1.2",
text: "Some text",
selected: false,
close: false
},
{
id: "ws13",
number: "1.3",
text: "Some text",
selected: false,
close: false
}
]
},
{
id: "pod2",
number: 2,
selected: false,
"workstations": [
{
id: "ws21",
number: "2.1",
text: "Some text",
selected: false,
close: false
},
{
id: "ws22",
number: "2.2",
text: "Some text",
selected: false,
close: false
},
{
id: "ws23",
number: "2.3",
text: "Some text",
selected: false,
close: false
}
]
}
]
},
computed: {
selection(){
if(this.workstations !== null && this.pod !== null){
let s = this.pods.filter(p => p.id === this.pod).map(p => {
let r = {...p}
r.workstations = p.workstations.filter(w => w.id === this.workstation)
return r
})
if (s.length === 1) return s[0]
}
return false
}
},
methods: {
selectWS(workstations, pods) {
if (this.pod == pods.id && this.workstation == workstations.id){
this.pod = null;
this.workstation = null;
} else{
this.pod = pods.id;
this.workstation = workstations.id;
}
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.1.7/js/uikit.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.1.7/css/uikit.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<div uk-grid class="card-body">
<div class="uk-width-1-4#m">
<div>1. Select an Item</div>
<div class="pods" v-for="pod in pods" :key="pod.id" :id="pod.id">
<div class="ws-in-col" v-for="workstations in pod.workstations" :key="workstations.id" :id="workstations.id" #click="selectWS(workstations, pod)">
<div class="ws ws-number uk-text-default">{{workstations.number}}</div>
</div>
</div>
</div>
<div class="uk-width-1-4#m">
<div>2. Selected Item</div>
<div v-if="selection" class="pods">
<div class="ws-in-col">
<div v-if="selection.workstations.length > 0">
<div class="group">
<div class="ws ws-number uk-text-default">{{selection.workstations[0].number}}</div>
<div class="ws ws-number uk-text-default">{{selection.workstations[0].text}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
You have to simply set the last selected pod/workstation and then show its properties (please run the snippet in full page otherwise you will be unable to see the right column):
new Vue({
el: "#app",
data: {
selectedPod: null,
selectedWorkstation: null,
pods: [
{
id: "pod1",
number: 1,
selected: false,
"workstations": [
{
id: "ws11",
number: "1.1",
text: "Some text 1",
selected: false,
close: false
},
{
id: "ws12",
number: "1.2",
text: "Some text 2",
selected: false,
close: false
},
{
id: "ws13",
number: "1.3",
text: "Some text 3",
selected: false,
close: false
}
]
},
{
id: "pod2",
number: 2,
selected: false,
"workstations": [
{
id: "ws21",
number: "2.1",
text: "Some text 4",
selected: false,
close: false
},
{
id: "ws22",
number: "2.2",
text: "Some text 5",
selected: false,
close: false
},
{
id: "ws23",
number: "2.3",
text: "Some text 6",
selected: false,
close: false
}
]
}
]
},
methods: {
selectWS(workstation, pod) {
this.selectedPod = pod;
this.selectedWorkstation = workstation;
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.1.7/js/uikit.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.1.7/css/uikit.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div uk-grid class="card-body">
<div class="uk-width-1-4#m">
<div>1. Select an Item</div>
<div class="pods" v-for="pod in pods" :key="pod.id">
<div class="ws-in-col" v-for="workstation in pod.workstations" :key="workstation.id" #click="selectWS(workstation, pod)">
<div class="ws ws-number uk-text-default">
{{workstation.number}}
{{workstation.text}}
</div>
</div>
</div>
</div>
<div class="uk-width-1-4#m">
<div>2. Selected Item</div>
<div class="pods" v-if="selectedWorkstation">
<div class="ws-in-col">
<div>
<div class="group">
<div class="ws ws-number uk-text-default">{{selectedWorkstation.number}}</div>
<div class="ws ws-number uk-text-default">{{selectedWorkstation.text}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>