Custom radio component in latest Vue would not function at all in latest Safari - vue.js

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>

Related

Add Close Button to Vue with Popper.js Modal Component

UPDATED Here is a codesandbox recreating the issue - Modal Does not hide: https://codesandbox.io/s/vue3-popperjs-modal-64762?file=/src/components/Modal.vue
I am new to Vue and cannot figure out how to Close the modal from the Hide Modal button within the component. I am using Vue.js and Popper.js for a modal/dropdown component.
I have tried emitting, rels and passing a prop but cannot get it to function correctly. I stripped it down to the shell code below.
** Main Component **
<template>
<div>
<Head title="Main" />
<div class="flex items-center">
<div class="flex">
<dropdown :auto-close="false" class="focus:z-10 px-4 hover:bg-gray-100 focus:border-white rounded-l focus:ring md:px-6" placement="auto">
<template #default>
<div class="flex">
<button id="open" class="btn-indigo" type="button">Open Modal</button>
</div>
</template>
<template #dropdown>
<div class="mt-2 px-4 py-6 w-screen bg-white rounded shadow-xl" style="maxWidth: 600px">
<h1>Modal Content</h1>
<button id="close" class="btn-indigo" type="button">Hide Modal</button>
</div>
</template>
</dropdown>
</div>
</div>
</div>
</template>
<script>
import { Head } from '#inertiajs/inertia-vue3'
import Dropdown from '#/Shared/Dropdown'
export default {
components: {
Head,
Dropdown,
},
}
</script>
** And Dropdown Component **
<template>
<button type="button" #click="show = true">
<slot />
<teleport v-if="show" to="#dropdown">
<div>
<div style="position: fixed; top: 0; right: 0; left: 0; bottom: 0; z-index: 99998; background: black; opacity: 0.2" #click="show = false" />
<div ref="dropdown" style="position: absolute; z-index: 99999" #click.stop="show = !autoClose">
<slot name="dropdown" />
</div>
</div>
</teleport>
</button>
</template>
<script>
import { createPopper } from '#popperjs/core'
export default {
props: {
placement: {
type: String,
default: 'bottom-end',
},
autoClose: {
type: Boolean,
default: true,
},
},
data() {
return {
show: false,
}
},
watch: {
show(show) {
if (show) {
this.$nextTick(() => {
this.popper = createPopper(this.$el, this.$refs.dropdown, {
placement: this.placement,
modifiers: [
{
name: 'preventOverflow',
options: {
altBoundary: true,
},
},
],
})
})
} else if (this.popper) {
setTimeout(() => this.popper.destroy(), 100)
}
},
},
mounted() {
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.show = false
}
})
},
}
</script>

Vue-MultiSelect Checkbox binding

The data properties of the multi-select component does not update on change. Check-boxes doesn't update on the front-end.
Expected Behavior: The check-boxes should get ticked, when clicked.
Link to code: https://jsfiddle.net/bzqd19nt/3/
<div id="app">
<multiselect
select-Label=""
selected-Label=""
deselect-Label=""
v-model="value"
:options="options"
:multiple="true"
track-by="library"
:custom-label="customLabel"
:close-on-select="false"
#select=onSelect($event)
#remove=onRemove($event)
>
<span class="checkbox-label" slot="option" slot-scope="scope" #click.self="select(scope.option)">
{{ scope.option.library }}
<input class="test" type="checkbox" v-model="scope.option.checked" #focus.prevent/>
</span>
</multiselect>
<pre>{{ value }}</pre>
</div>
new Vue({
components: {
Multiselect: window.VueMultiselect.default
},
data: {
value: [],
options: [
{ language: 'JavaScript', library: 'Vue.js', checked: false },
{ language: 'JavaScript', library: 'Vue-Multiselect', checked: false },
{ language: 'JavaScript', library: 'Vuelidate', checked: false }
]
},
methods: {
customLabel(option) {
return `${option.library} - ${option.language}`;
},
onSelect(option) {
console.log('Added');
option.checked = true;
console.log(`${option.library} Clicked!! ${option.checked}`);
},
onRemove(option) {
console.log('Removed');
option.checked = false;
console.log(`${option.library} Removed!! ${option.checked}`);
}
}
}).$mount('#app');
In your code you call onSelect and try to change the option argument of this function inside the function:
option.checked = true;
This affects only the local variable option (the function argument). And doesn't affect objects in options array in the data of the Vue instance, the objects bound with checkboxes. That's why nothing happens when you click on an option in the list.
To fix it find the appropriate element in options array and change it:
let index = this.options.findIndex(item => item.library==option.library);
this.options[index].checked = true;
Here is the code snippet with fix:
new Vue({
components: {
Multiselect: window.VueMultiselect.default
},
data: {
value: [],
options: [
{ language: 'JavaScript', library: 'Vue.js', checked: false },
{ language: 'JavaScript', library: 'Vue-Multiselect', checked: false },
{ language: 'JavaScript', library: 'Vuelidate', checked: false }
]
},
methods: {
customLabel (option) {
return `${option.library} - ${option.language}`
},
onSelect (option) {
console.log("Added");
let index = this.options.findIndex(item => item.library==option.library);
this.options[index].checked = true;
console.log(option.library + " Clicked!! " + option.checked);
},
onRemove (option) {
console.log("Removed");
let index = this.options.findIndex(item => item.library==option.library);
this.options[index].checked = false;
console.log(option.library + " Removed!! " + option.checked);
}
}
}).$mount('#app')
* {
font-family: 'Lato', 'Avenir', sans-serif;
}
.checkbox-label {
display: block;
}
.test {
position: absolute;
right: 1vw;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link href="https://unpkg.com/vue-multiselect#2.0.2/dist/vue-multiselect.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue-multiselect#2.0.3/dist/vue-multiselect.min.js"></script>
<div id="app">
<multiselect
select-Label=""
selected-Label=""
deselect-Label=""
v-model="value"
:options="options"
:multiple="true"
track-by="library"
:custom-label="customLabel"
:close-on-select="false"
#select=onSelect($event)
#remove=onRemove($event)
>
<span class="checkbox-label" slot="option" slot-scope="scope" #click.self="select(scope.option)">
{{ scope.option.library }}
<input class="test" type="checkbox" v-model="scope.option.checked" #focus.prevent/>
</span>
</multiselect>
<pre>{{ value }}</pre>
</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>

Multi selected Filter in Vue.js

I have a selection of checkbox groups (hotel type and locations) and I want to filter the results based on what has been selected. How can I add my selectedLocations and types to the filteredHotels() method and get a filtered result. Sydney Hotels, Sydney Backpackers, Sydney or Melbourne hotels or all Hotels if only hotels is selected.
HTML
<div>
<div class="row pt-5">
<div class="col">
<h5>Locations</h5>
<label v-for="(value, key) in locations">
{{value}}
<input type="checkbox" :value="value" v-model="selectedLocations">
</label>
{{selectedLocations}}
</div>
</div>
<div class="row pt-5">
<div class="col">
<h5>Type</h5>
<label v-for="(value, key) in types">
{{value}}
<input type="checkbox" :value="value" v-model="selectedTypes">
</label>
{{selectedTypes}}
</div>
</div>
<transition-group class="row" style="margin-top:30px;" name="list" tag="div" mode="out-in">
<div class="col-sm-4 pb-3 list-item" v-for="(hotel, index) in filteredHotels" :key="index">
<div class="sau-card">
<i class="fal fa-server fa-3x"></i>
<h2>{{hotel.name}}</h2>
<p>{{hotel.type}}</p>
<p>{{hotel.location}}</p>
</div>
</div>
</transition-group>
</div>
Data
data() {
return {
locations:['Sydney','Melbourne'],
types:['backpackers','hotel','resort'],
selectedLocations:[],
selectedTypes:[],
hotels:[
{name:'a Hotel',location:'Sydney', type:'backpackers'},
{name:'b Hotel',location:'Sydney', type:'hotel'},
{name:'c Hotel',location:'Sydney', type:'resort'},
{name:'d Hotel',location:'Melbourne',type:'hotel'},
{name:'e Hotel',location:'Melbourne', type:'resort'},
{name:'f Hotel',location:'Melbourne', type:'hotel'},
]
}
},
Computed
computed:{
filteredHotels(){
if(this.selectedLocations.length){
return this.hotels.filter(j => this.selectedLocations.includes(j.location))
}
else if(this.selectedTypes.length){
return this.hotels.filter(j => this.selectedTypes.includes(j.type))
}
else{
return this.hotels;
}
}
}
Fiddle
Pass your data via binding this or using ()=>, e.g:
filteredHotels(){
return this.hotels.filter(function (el) {
return this.selectedLocations.includes(el.location)
|| this.selectedTypes.includes(el.type)
}.bind(this));
}
Fiddle
Here is a working example:
I've updated the condition under computed property as below: Include only location which is being selected. Instead of just returning the current selected location.
computed:{
filteredHotels(){
console.log(this.selectedLocations);
if(!this.selectedLocations.length) {
return this.hotels;
} else {
return this.hotels.filter(j => this.selectedLocations.includes(j.location))
}
}
}
new Vue({
el: "#app",
data: {
locations: ['Sydney', 'Melbourne'],
types: ['backpackers', 'hotel', 'resort'],
selectedLocations: [],
selectedTypes: [],
filtersAppied: [],
hotels: [{
name: 'a Hotel',
location: 'Sydney',
type: 'backpackers'
},
{
name: 'b Hotel',
location: 'Sydney',
type: 'hotel'
},
{
name: 'c Hotel',
location: 'Sydney',
type: 'resort'
},
{
name: 'd Hotel',
location: 'Melbourne',
type: 'hotel'
},
{
name: 'e Hotel',
location: 'Melbourne',
type: 'resort'
},
{
name: 'f Hotel',
location: 'Melbourne',
type: 'hotel'
},
]
},
methods: {
setActive(element) {
if (this.filtersAppied.indexOf(element) > -1) {
this.filtersAppied.pop(element)
} else {
this.filtersAppied.push(element)
}
},
isActive: function (menuItem) {
return this.filtersAppied.indexOf(menuItem) > -1
},
},
computed: {
filterApplied: function() {
if (this.filtersAppied.indexOf(element) > -1) {
this.filtersAppied.pop(element)
} else {
this.filtersAppied.push(element)
}
},
filteredHotels: function() {
return this.hotels.filter(hotel => {
return this.filtersAppied.every(applyFilter => {
if (hotel.location.includes(applyFilter)) {
return hotel.location.includes(applyFilter);
}
if (hotel.type.includes(applyFilter)) {
return hotel.type.includes(applyFilter);
}
});
});
}
}
})
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/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<div class="row pt-5">
<div class="col">
{{filteredHotels}}
<h5>Locations</h5>
<label v-for="(value, key) in locations">
{{value}}
<input type="checkbox" :value="value" v-model="selectedLocations" :checked="isActive(value)" #click="setActive(value)">
</label> {{selectedLocations}}
</div>
</div>
<div class="row pt-5">
<div class="col">
<h5>Type</h5>
<label v-for="(value, key) in types">
{{value}}
<input type="checkbox" :value="value" v-model="selectedTypes"
:checked="isActive(value)"#click="setActive(value)">
</label> {{selectedTypes}}
</div>
</div>
<div class="row">
<div class="col-sm-3 pb-4" v-for="hotel in filteredHotels">
<div class="card text-center">
<h1>{{hotel.name}}</h1>
<p>{{hotel.location}}</p>
</div>
</div>
</div>
</div>
</div>
Hope this helps!

Trouble with V-for

I'm trying to loop through a customer information array using Q-Cards. I have tested to make sure my customers array always gets updated and has the values I want from my NodeJS Server but it refuses to display any Q-Cards. I have tried many things but I cannot get it to work at all. Any advise or input would be great. Thanks in advance
<template>
<div id="maincustomer">
<div id="customerbox">
<div class="row">
<q-input class="inputspace" placeholder="First Name" color="secondary" v-model="fname" #input="dataentered()"/>
<q-input class="inputspace" placeholder="Last Name" color="secondary" v-model="lname" #input="dataentered()"/>
<q-btn class="inputspace" icon="search" color="secondary" :disable="buttonenable" #click="findcustomer()"/>
</div>
<div class="row">
<q-card color="secondary" dark class="q-ma-sm" v-for="customer in customers" :key="customer.CustomerID">
<q-card-title>
{{ customer.FirstName }} {{ customer.LastName }}
<span slot="subtitle">Phone Number: {{ customer.PhoneNumber }}</span>
<q-icon slot="right" name="person" />
</q-card-title>
<q-card-main>
{{ customer.Address }}
</q-card-main>
<q-card-separator />
</q-card>
</div>
</div>
</div>
</template>
<script>
export default {
// name: 'ComponentName',
data () {
return {
buttonenable: true,
fname: "",
lname: "",
customers: []
}
},
methods: {
dataentered: function () {
if(this.fname=="" && this.lname=="")
{
this.buttonenable=true
}
else {
this.buttonenable=false
}
},
findcustomer: function () {
this.$Socket.emit('findcustomer', {
fname: this.fname,
lname: this.lname
}, function(customerlist) {
console.log(customerlist)
this.customers=customerlist
}
)
}
}
}
</script>
<style>
#customerbox {
max-width: 700px;
display: inline-block;
}
.inputspace {
margin: 5px;
}
#maincustomer {
text-align: center;
}
</style>
Probably you lost this context here:
function(customerlist) {
console.log(customerlist)
this.customers=customerlist
}
Because function has own this context. To fix it you should use arrow function. For your case:
findcustomer() {
this.$Socket.emit('findcustomer', {
fname: this.fname,
lname: this.lname
}, (customerlist) => {
console.log(customerlist)
this.customers=customerlist
}
)
}