Trouble with V-for - vue.js

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
}
)
}

Related

How to restrict user to enter only 30 characters in Vuejs?

<script>
export default {
name: "Register",
props: {
msg: String,
},
};
</script>
-------------main.js---------------
new Vue({
data:{
max:30,
text:''
},
render:h => h(App),
}).$mount('#app'
<template>
<div class="pop-up-mask">
{{ msg }}
<div class="pop-up">
<input type="text" class="input-section"
placeholder="Enter your Name" :maxlength="max" v-model="text" />
</div>
</template>
If the user tries to enter more than 30 characters, user should get an error message: you can only enter 30 characters. Try with above logic like maxlength="max" v-model="text"
I had done something similar in the past, so I built on that component (plus some research) to build this component that solves the problem.
<template>
<div class="input-max">
<div class="form-row">
<div class="col-md-8">
<input class="form-control" type="text" placeholder="Address"
v-model="address" #keyup="updateAddress">
</div>
<div class="col-md-4">
<span v-if="displayWarning" class="error-msg">* You can only enter {{ maxLength }} characters</span>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
address: '',
previousAddress: '',
maxLength: 30,
displayWarning: false
}
},
methods: {
updateAddress(event) {
let newValue = event.target.value;
if (newValue.length > this.maxLength) {
event.preventDefault()
this.address = this.previousAddress;
this.displayWarning = true;
}
else {
this.address = newValue;
this.previousAddress = newValue;
this.displayWarning = false;
}
}
}
}
</script>
<style scoped>
.error-msg {
color: red;
}
</style>

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>

Vue components all acting as one

I have three components. All of them bind a value.
Below is the blade file with the three components
Blade File:
<div id="app">
<div class="row">
<div class="col-4">
<crud-component :type="'tower'"></crud-component>
</div>
<div class="col-4">
<crud-component :type="'country'"></crud-component>
</div>
<div class="col-4">
<crud-component :type="'department'"></crud-component>
</div>
</div>
</div>
All of these components are dynamic, they only rely on that binded value called :type
The Vue file:
<template>
<div>
<h3>{{capitalize(type)}} <button class="btn btn-primary btn-sm" #click="showAddModal()">Add</button></h3>
<datatable :columns="columns" :sortKey="sortKey" :sortOrders="sortOrders">
<tbody v-if="loading">
<tr>
<td colspan="11"><div class="lds-dual-ring mx-auto"></div></td>
</tr>
</tbody>
<tbody v-else>
<tr v-for="data in retData" :key="data.id">
<td> {{data.id}}</td>
<td> {{data.name}}</td>
<td>
<button class="btn btn-warning btn-sm" #click="showEditModal(data)"> Edit </button>
<button class="btn btn-danger btn-sm" #click="showDeleteModal(data)"> Delete </button>
</td>
</tr>
</tbody>
</datatable>
<!-- MODALS -->
<modal :name="type + '-add-modal'" height="auto">
<div class="row p-3">
<div class="col-12">
<h3>Add {{capitalize(type)}}</h3>
<hr>
<form>
<div class="form-group">
<label for="add-item">{{capitalize(type)}} Name</label>
<input v-validate="'required'" type="text" name="item-name" class="form-control" id="add-item-name" required>
<small class="form-text text-danger">{{errors.first('item-name')}}</small>
</div>
<button type="button" class="float-right btn btn-sm btn-primary" #click="store">Submit</button>
</form>
</div>
</div>
</modal>
<modal :name="type + '-edit-modal'" height="auto">
<div class="row p-3">
<div class="col-12">
<h3>Edit {{capitalize(type)}}</h3>
<hr>
<form>
<div class="form-group">
<label for="edit-item">{{capitalize(type)}} Name</label>
<input v-validate="'required'" name="item-name" type="text" class="form-control" id="edit-item-name" v-model="currentItem.name" required>
<small class="form-text text-danger">{{errors.first('item-name')}}</small>
</div>
<button type="button" class="float-right btn btn-sm btn-primary" #click="store">Submit</button>
</form>
</div>
</div>
</modal>
<modal :name="type + '-delete-modal'" height="auto">
<div class="row p-3">
<div class="col-12">
<h3>Delete {{capitalize(type)}}</h3>
<hr>
<p class="lead">Are you sure you want to delete <strong>{{currentItem.name}}</strong>?</p>
<button type="button" class="float-right btn btn-sm btn-primary" #click="destroy">Submit</button>
</div>
</div>
</modal>
</div>
</template>
<script>
import Datatable from './utilities/DatatableComponent.vue';
import Pagination from './utilities/PaginationComponent.vue';
export default {
components: {
'datatable': Datatable,
'pagination': Pagination,
},
props: ['type'],
mounted() {
this.get(this.type);
},
data() {
let sortOrders = {};
let columns = [
{label: 'ID', name:'id', isSortable: true},
{label: 'NAME', name:'name', isSortable: true},
{label: 'ACTIONS', name:'actions', isSortable: false}
];
columns.forEach(column => {
sortOrders[column.name] = -1;
});
return {
loading: true,
currentItem: '',
isUpdate: false,
sortKey: 'id',
columns: columns,
sortOrders: sortOrders,
tableData: {
draw: 0,
length: 10,
search: '',
column: 'id',
dir: 'desc',
},
pagination: {
lastPage: '',
currentPage: '',
total: '',
lastPageUrl: '',
prevPageUrl: '',
nextPageUrl: '',
from: '',
to: '',
},
retData: []
};
},
methods: {
showEditModal(item) {
this.currentItem = item;
this.$modal.show(this.type + '-edit-modal');
this.isUpdate = true;
},
showAddModal() {
this.$modal.show(this.type + '-add-modal');
},
store() {
this.$validator.validate().then(valid => {
if(!valid) {
return;
} else {
if(this.isUpdate == true) {
axios.post('/api/crud/store', {
type: this.type,
item: this.currentItem,
isUpdate: this.isUpdate
}).then(response => {
this.get(this.type);
this.$modal.hide(this.type + '-edit-modal');
this.isUpdate = false;
this.currentItem = '';
});
} else {
axios.post('/api/crud/store', {
type: this.type,
name: $('#add-item-name').val(),
isUpdate: this.isUpdate
}).then(response => {
this.get(this.type);
this.$modal.hide(this.type + '-add-modal');
});
}
}
});
},
showDeleteModal(item) {
this.currentItem = item;
this.$modal.show(this.type + '-delete-modal');
},
destroy() {
axios.delete('/api/crud/delete', {
data: {
type: this.type,
item: this.currentItem
}
}).then(response => {
this.$modal.hide(this.type + '-delete-modal')
this.currentItem = '';
this.get(this.type)
});
},
capitalize(s) {
if (typeof s !== 'string') return ''
return s.charAt(0).toUpperCase() + s.slice(1)
},
get(type) {
axios.interceptors.request.use(config => {
NProgress.start();
this.loading = true;
return config;
});
axios.interceptors.response.use(response => {
NProgress.done();
this.loading = false;
return response;
});
this.tableData.draw++;
axios.post('/api/crud', {
type: type,
tableData: this.tableData
}).then(response => {
let data = response.data;
if(this.tableData.draw == data.draw) {
this.retData = data.data.data
}
console.log('Done');
});
},
}
}
</script>
<style>
.lds-dual-ring {
display: block;
width: 64px;
height: 64px;
}
.lds-dual-ring:after {
content: " ";
display: block;
width: 46px;
height: 46px;
margin: 5px;
border-radius: 50%;
border: 5px solid #000;
border-color: #000 transparent #000 transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
#keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
The problem is, when I'm trying to update, fetch, or do anything with one component.
It runs all of the API calls to fetch their specific data.
I just want to have that one table load when it's fetching data.
As mentioned in my comment above, you are adding interceptors to your Axios instance every time any of your components calls its get method.
The interceptor code runs on every request / response made through Axios, no matter where it comes from. This is why all your components appear to be loading when a request is made.
I suggest you remove the interceptors and change your code to
get(type) {
NProgress.start()
this.loading = true
this.tableData.draw++
axios.post('/api/crud', {
type: type,
tableData: this.tableData
}).then(response => {
NProgress.done()
this.loading = false
let data = response.data;
if(this.tableData.draw == data.draw) {
this.retData = data.data.data
}
})
}
Data needs to be a function.
data () {
return {
...
}
}
You can't simply have a bunch of variables and also a function like you have. You need to refactor your component.

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!