Add class on click in vuejs - vue.js

My vue template:
<div
class="col-sm-4 col-xs-6 thumb"
v-for="(photo, index) in photos"
#click.prevent="check(index)"
>
<a class="thumbnail" :class="{'active': photo.checked}">
<img class="img-responsive" :src="photo.picture" alt="">
</a>
</div>
My check() method:
check(index) {
if(!("checked" in this.photos[index]))
this.photos[index].checked = true
else
this.photos[index].checked = !this.photos[index].checked
},
Everything seems correct but it's not working. What could the problem be?

Vue cannot detect changes to an index of an array.
Get a reference to the photo object of the index passed to check() and then use Vue.set() to update the array like so:
check(index) {
let photo = this.photos[index];
if (!("checked" in photo)) {
photo.checked = true
} else {
photo.checked = !photo.checked
}
Vue.set(this.photos, index, photo);
},
Here's a fiddle.

How about just #click.prevent="$set(photo, 'checked', !photo.checked)" and forget the handler?
<div class="col-sm-4 col-xs-6 thumb" v-for="(photo, index) in photos"
#click.prevent="$set(photo, 'checked', !photo.checked)">
<a class="thumbnail" :class="{'active': photo.checked}">
<img class="img-responsive" :src="photo.picture" alt="">
</a>
</div>
If you want to use the handler:
<div class="col-sm-4 col-xs-6 thumb" v-for="(photo, index) in photos"
#click.prevent="check(photo)">
And
check(photo) {
this.$set(photo, 'checked', !photo.checked)
},

Related

Vue js slider with left and right

export default {
name: 'MovieSlider',
data() {
return {
index: 0,
imgs: [
'https://7themes.su/_ph/40/303277371.jpg',
'https://7themes.su/_ph/23/730607462.jpg',
'https://7themes.su/_ph/23/730607462.jpg',
],
};
},
computed: {
sideSlide() {
return {
transform: `translateX(${-this.index * 100}vw)`,
};
},
},
methods: {},
left() {
this.index++;
if (this.index > this.imgs.length - 1) {
this.index = 0;
}
this.sideSlide;
},
right() {
this.index--;
if (this.index < 0) {
this.index = this.imgs.length - 1;
}
this.sideSlide;
},
};
<template>
<div class="carusel">
<div class="image-container imgs">
<button class="left buttons" #click="left">
<i class="fa-solid fa-angle-left"></i>
</button>
<div v-for="item in imgs" :key="item.id" :style="sideSlide">
<img :src="item" />
</div>
<button class="right buttons" #click="right">
<i class="fa-solid fa-chevron-right"></i>
</button>
</div>
<div class="popular_films">
<div class="content">
<p class="film_name">Film Name</p>
<p class="film_info">
<i class="fa-solid fa-star"></i>
<span>FIlm vote</span>
<span>Film time</span>
<span>Film year</span>
</p>
<span class="play_treiler"
>Trailer<i class="fa-solid fa-play"></i
></span>
</div>
</div>
</div>
</template>
Hi friends, I want to make slider with vue but something is going wrong, after pressing right or left i want to give a style(size) to image that slides to another image but something is going wrong, in here you can find all of the code that i used in this component, Thank you.

Cannot read property 'focus' of undefined in VUE When setting focus to button

I am new to vue I have component which if the endpoint fails, calls my generic 'Error' modal. All this is working fine but I keep getting the following error:
Cannot read property 'focus' of undefined
This only happens for the else part of my method function.
For this specific issue is I my 'failedPrcess' equals any of the following, this is when I get is, all others are fine:
existOrderSearchProdOrders
stockSearchStockLevels
cartFetchCouriers
Code
<template>
<div class="modal fade danger-modal" id="errorModal" tabindex="-1" role="dialog" aria-labelledby="errorModalTitle" aria-hidden="true"
data-keyboard="false" data-backdrop="static" style="z-index: 99999">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content danger-modal-content">
<div class="modal-header danger-modal-headerfooter">An error has occurred</div>
<div class="modal-body">
<p v-if="failedProcess === 'appGetAccount' || failedProcess === 'existOrderSearchProdOrders' || failedProcess === 'stockSearchStockLevels'
|| failedProcess === 'cartFetchCouriers'">
{{ contactTxt | capitalize }}
</p>
<p v-else-if="errorCount < 3">If the error continues, {{ contactTxt }}</p>
<p v-else>As the error has continued, {{ contactTxt }}</p>
<p>
<b>
01234 567890
<br />
Open from 00:00 to 07:00
</b>
</p>
<p>Advising of what you were doing when the error occurred.</p>
</div>
<div class="modal-footer danger-modal-headerfooter">
<a v-if="failedProcess === 'appGetAccount'" ref="logoutButton" class="btn btn-primary" :class="logoutButtClicked" #click="logoutClicked = true" href="/site/logout">
<span v-if="!logoutClicked" id="logoutButtonLabel">Logout</span>
<span v-else id="logoutSpinner">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Logging out
</span>
</a>
<router-link v-else-if="failedProcess === 'fetchOrderReportDetails'" to="/review" tag="button"
ref="existOrdersButton" class="btn btn-primary" type="button" data-dismiss="modal" #click.native="closeButton">
Return to existing orders
</router-link>
<button v-else-if="errorCount < 3 && (failedProcess !== 'productsFetchProducts' && failedProcess !== 'existOrderSearchProdOrders'
&& failedProcess !== 'stockSearchStockLevels' && failedProcess !== 'cartFetchCouriers')" ref="closeButton" class="btn btn-primary"
type="button" data-dismiss="modal" #click="closeButton">
Close
</button>
<router-link v-else to="/" tag="button" ref="homeButton" class="btn btn-primary" type="button" data-dismiss="modal" #click="closeButton">
Return to homepage
</router-link>
</div>
</div>
</div>
</div>
</template>
<script>
import * as params from '../params';
export default {
name: "ErrorModal",
data() {
return {
contactTxt: 'please contact us on:',
errorCount: 0,
failedProcess: '',
}
},
mounted() {
VueEvent.$on('show-error-modal', (failedProcess) => {
if (this.failedProcess !== failedProcess) {
this.errorCount = 0;
}
this.failedProcess = failedProcess;
$('#errorModal').modal('show').on('shown.bs.modal', this.focus);
});
},
methods: {
focus() {
if (this.failedProcess === 'appGetAccount') {
this.$refs.logoutButton.focus();
} else if (this.failedProcess === 'fetchOrderReportDetails') {
this.$refs.existOrdersButton.$el.focus();
} else if (this.errorCount < 3 && this.failedProcess !== 'productsFetchProducts') {
this.$refs.closeButton.focus();
} else {
this.$refs.homeButton.$el.focus();
}
},
}
}
</script>`enter code here`
I've tried using v-if before and I also had similar problems and the best solution I found was, instead of using v-if/v-else-if/v-else, use v-show instead to perform conditional rendering.
Also, as the Vue.js doc says:
Generally speaking, v-if has higher toggle costs while v-show has higher initial render costs. So prefer v-show if you need to toggle something very often, and prefer v-if if the condition is unlikely to change at runtime.

Bind class item in the loop

i want to bind my button only on the element that i added to the cart, it's working well when i'm not in a loop but in a loop anything happen. i'm not sure if it was the right way to add the index like that in order to bind only the item clicked, if i don't put the index every button on the loop are binded and that's not what i want in my case.
:loading="isLoading[index]"
here the vue :
<div class="container column is-9">
<div class="section">
<div class="columns is-multiline">
<div class="column is-3" v-for="(product, index) in computedProducts">
<div class="card">
<div class="card-image">
<figure class="image is-4by3">
<img src="" alt="Placeholder image">
</figure>
</div>
<div class="card-content">
<div class="content">
<div class="media-content">
<p class="title is-4">{{product.name}}</p>
<p class="subtitle is-6">Description</p>
<p>{{product.price}}</p>
</div>
</div>
<div class="content">
<b-button class="is-primary" #click="addToCart(product)" :loading="isLoading[index]"><i class="fas fa-shopping-cart"></i> Ajouter au panier</b-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
here the data :
data () {
return {
products : [],
isLoading: false,
}
},
here my add to cart method where i change the state of isLoading :
addToCart(product) {
this.isLoading = true
axios.post('cart/add-to-cart/', {
data: product,
}).then(r => {
this.isLoading = false
}).catch(e => {
this.isLoading = false
});
}
You can change your isLoading to an array of booleans, and your addToCart method to also have an index argument.
Data:
return {
// ...
isLoading: []
}
Methods:
addToCart(product, index) {
// ...
}
And on your button, also include index:
#click="addToCart(product, index)"
By changing isLoading to an empty array, I don't think isLoading[index] = true will be reactive since index on isLoading doesn't exist yet. So you would use Vue.set in your addToCart(product, index) such as:
this.$set(this.isLoading, index, true)
This will ensure that changes being made to isLoading will be reactive.
Hope this works for you.
add on data productsLoading: []
on add to cart click, add loop index to productsLoading.
this.productsLoading.push(index)
after http request done, remove index from productsLoading.
this.productsLoading.splice(this.productoading.indexOf(index), 1)
and check button with :loading=productsLoading.includes(index)
You can create another component only for product card,
for better option as show below
Kindly follow this steps.
place the content of card in another vue component as shown below.
<!-- Product.vue -->
<template>
<div class="card">
<div class="card-image">
<figure class="image is-4by3">
<img src="" alt="Placeholder image">
</figure>
</div>
<div class="card-content">
<div class="content">
<div class="media-content">
<p class="title is-4">{{product.name}}</p>
<p class="subtitle is-6">Description</p>
<p>{{product.price}}</p>
</div>
</div>
<div class="content">
<b-button class="is-primary" #click="addToCart(product)" :loading="isLoading"><i class="fas fa-shopping-cart"></i> Ajouter au panier</b-button>
</div>
</div>
</div>
</templete>
<script>
export default {
name: "Product",
data() {
return {
isLoading: false
}
},
props: {
product: {
type: Object,
required: true
}
},
methods: {
addToCart(product) {
this.isLoading = true
axios.post('cart/add-to-cart/', {
data: product,
}).then(r => {
this.isLoading = false
}).catch(e => {
this.isLoading = false
});
}
}
}
</script>
Change your component content as shown below.
<template>
<div class="container column is-9">
<div class="section">
<div class="columns is-multiline">
<div class="column is-3" v-for="(product, index) in computedProducts">
<product :product="product" />
</div>
</div>
</div>
</div>
</templete>
<script>
import Product from 'path to above component'
export default {
components: {
Product
}
}
</script>
so in the above method you can reuse the component in other components as well.
Happy coding :-)

Populate Knockout's observable array from Razor

I'm trying to implement error handling in ASP.NET so that if there is an error the user will get the error message, then be able to go back and have the previous state restored. I'm using ASP.NET Core and Knockout (not my implementation). I want to update "signerFields" with the model from the server (Model.SignersJson). How would I do this?
Signer.js
function SignerViewModel() {
var self = this;
self.signerFields = ko.observableArray([]);
self.guarantorFields = ko.observableArray([]);
self.companyGuarantorFields = ko.observableArray([]);
...
Signer.cshtml
<div data-bind="foreach: signerFields, visible: signerFields().length > 0">
<div class="row">
<div class="col-lg-10">
<div>
#*Header Company signers section*#
<div class="row" data-bind="visible: isCompany() && !anySigner() && !isInvitation()" style="display: none">
<div class="col-lg-4">
<b>FullName</b>
</div>
#*<div class="col-lg-3">
<b>LastName </b>
</div>*#
<div class="col-lg-4">
<b>Role </b>
</div>
<div class="col-lg-3">
<b>Contact_Information</b>
</div>
</div>
</div>
</div>
...
#section scripts
{
<script src="~/Scripts/Signer.js"></script>
var serverSigners = JSON.parse(#Html.Raw(Json.Encode(Model.SignersJson)));
var observableData = ko.mapping.fromJS(serverSigners);
var viewModel = new SignerViewModel();
viewModel.signerFields(observableData); // <-- How?
}
I get no error messages, nothing.
There is a lot of unknowns with this one, but here is a working example using the information we have at hand. One thing I noticed was when creating this sample was that I assumed the data comming from Razor was in an array. and when the array is passed into the mapping component it comes out as an observable array. This meant that the data going into the signerFields was probably not what you were expecting and ended up having an observableArray with one object which itself was an observable array. Adding round brackets to observableData() means that you get the data out of the observable and you can then pass it into the signerFields as an array of objects.
Hope that made sense.
function SignerViewModel() {
var self = this;
self.signerFields = ko.observableArray([]);
self.guarantorFields = ko.observableArray([]);
self.companyGuarantorFields = ko.observableArray([]);
}
var serverSigners = [{'fullname':'Test Name', 'lastName': 'Name', 'role': 'Test Role', 'contactInformation': '123 Seasame Street NY, US', 'isCompany': true, 'anySigner': false, 'isInvitation': false}];
var observableData = ko.mapping.fromJS(serverSigners);
var viewModel = new SignerViewModel();
viewModel.signerFields(observableData());
ko.applyBindings(viewModel)
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<div data-bind="foreach: signerFields, visible: signerFields().length > 0">
<div class="row">
<div class="col-lg-10">
<div>
<div class="row" data-bind="visible: isCompany() && !anySigner() && !isInvitation()">
<div class="col-lg-4">
<b>FullName: </b><span data-bind="text: fullname"></span>
</div>
<div class="col-lg-3">
<b>LastName: </b><span data-bind="text: lastName"></span>
</div>
<div class="col-lg-4">
<b>Role: </b><span data-bind="text: role"></span>
</div>
<div class="col-lg-3">
<b>Contact Information: </b><span data-bind="text: contactInformation"></span>
</div>
</div>
</div>
</div>
</div>
</div>

Data Reactivity On Post Request Vue

Im having issues with my data not reacting correctly when a new object is added to the array of data. I am currently looping through an array of engagements that belong to a client like this
<div class="row mx-2 px-2 justify-content-between" v-if="!engagementLoaded">
<div class="card mb-3 shadow-sm col-lg-5 col-md-3 p-0" v-for="(engagement, index) in engagement" :key="index">
<div class="d-flex justify-content-between card-header">
<h3 class="m-0 text-muted">{{ index + 1 }}</h3>
<h5 class="align-self-center m-0"><span>Return Type: </span> {{ engagement.return_type }} </h5>
</div>
<div class="card-body text-left p-0 my-1">
<h5 class="p-4"><span>Year: </span> {{ engagement.year }} </h5>
<hr class="my-1">
<h5 class="p-4"><span>Assigned To: </span> {{ engagement.assigned_to }} </h5>
<hr class="my-1">
<h5 class="p-4"><span>Status: </span> {{ engagement.status}} </h5>
</div>
<div class="card-footer d-flex justify-content-between">
<router-link to="#" class="btn">View</router-link>
<router-link to="#" class="btn">Edit</router-link>
</div>
</div>
</div>
Everything works fine until I add a new engagement to the array. My AddEngagement Component is seperate but this is the form and script for it.
<form #submit.prevent="addEngagement" class="d-flex-column justify-content-center">
<div class="form-group">
<select class="form-control mb-3" id="type" v-model="engagement.return_type">
<option v-for="type in types" :key="type.id" :value="type">{{ type }}</option>
</select>
<input type="text" class="form-control mb-3" placeholder="Year" v-model="engagement.year">
<input type="text" class="form-control mb-3" placeholder="Assign To" v-model="engagement.assigned_to">
<input type="text" class="form-control mb-3" placeholder="Status" v-model="engagement.status">
<div class="d-flex justify-content-between">
<button type="submit" class="btn btn-primary d-flex justify-content-start">Create</button>
<router-link v-bind:to="'/client/' +client.id+ '/engagements'" class="btn btn-secondary float-right">Dismiss</router-link>
</div>
</div>
</form>
This is the addEngagement Method for the form
methods: {
addEngagement(e) {
if(!this.engagement.return_type || !this.engagement.year ){
return
} else {
this.$store.dispatch('addEngagement', {
id: this.idForEngagement,
client_id: this.client.id,
return_type: this.engagement.return_type,
year: this.engagement.year,
assigned_to: this.engagement.assigned_to,
status: this.engagement.status,
})
e.preventDefault();
}
e.preventDefault();
this.engagement = ""
this.idForEngagement++
this.$router.go(-1);
},
},
I think the issue is happening here but im not sure
this.$router.go(-1);
Ive tried this as well
this.$router.push({path: 'whatever route'})
and it did not change either
Somehow I need the parent component EngagementsList to react correctly to my newly added engagement that is submitted from the AddEngagement componenet if that makes sense..
here is the code for the addEngagement in the Vuex
addEngagement(context, engagement) {
axios.post(('/engagements'), {
client_id: engagement.client_id,
return_type: engagement.return_type,
year: engagement.year,
assigned_to: engagement.assigned_to,
status: engagement.status,
done: false
})
.then(response => {
context.commit('getClientEngagement', response.data)
})
.catch(error => {
console.log(error)
})
},
Although I can't see your code, I'm pretty certain I know the problem. You have an array but it's not reactive to the objects within. This is because Vue doesn't know to observe this array.
If you want your array to be reactive, use Vue.set from within your store model.
Vue.set(this/state, 'array_name', Array<Of objects>)
This will make sure vue watches for changes within the Array.
For your use case, you will need to append the engagement object to the existing array and then use that array:
const engagements = state.engagements
engagements.push(engagement)
Vue.set(state, 'engagements', engagements)
So i've tried different configurations of this. I changed my state description that the v-for is iterating to clientengagements
engagement now equals clientengagements
here is the mutation currently
getClientEngagements(state, engagement, clientengagements) {
state.clientengagements = clientengagements
clientengagements.push(engagement)
Vue.set(state, 'clientengagements', clientengagements)
},
Which is not working. Ive also tried
getClientEngagements(state, clientengagements) {
state.clientengagements = clientengagements
clientengagements.push(engagement)
Vue.set(state, 'clientengagements', clientengagements)
},
Which will tell me that "engagement" is not defined
If anyone is still able to help this is the Action
addEngagement(context, engagement) {
axios.post(('/engagements'), {
client_id: engagement.client_id,
return_type: engagement.return_type,
year: engagement.year,
assigned_to: engagement.assigned_to,
status: engagement.status,
done: false
})
.then(response => {
context.commit('getClientEngagements', response.data)
})
.catch(error => {
console.log(error)
})
},
The response.data is mutating my clientengagements array to just a single object of the newly added engagement..
context.commit('getClientEngagements`, response.data)
Is commiting to this mutation
getClientEngagements(state, engagement, clientengagements) {
state.clientengagements = clientengagements
clientengagements.push(engagement)
Vue.set(state, 'clientengagements', clientengagements)
},