Dynamically change icon variant in Vue - javascript

I have the following template which I'm trying to change dynamically when values in data has been changed:
<div class="vote-bar-content">
<button class="vote-btn" #click="upvote(news)">
<b-icon icon="chevron-up"
:variant="upvoteVariant(news)"></b-icon>
</button>
<span class="vote"> No. of vote goes here </span>
<button class="vote-btn" #click="downvote(news)" >
<b-icon icon="chevron-down"
:variant="downvoteVariant(news)"></b-icon>
</button>
</div>
What I am trying to accomplish is to change both the b-icon variants into their respective value according to upvoteVariant and downvoteVariant when the user user clicks on the respective vote-btn. Below are both functions that are bound to the b-icon elements which are listed on Vue methods:
upvoteVariant(news) {
if (this.votingList[news._id].upvoted) {
return 'warning'
}
else {
return ''
}
},
downvoteVariant(news) {
if (this.votingList[news._id].downvoted) {
return 'primary'
}
else {
return ''
}
}
When the button is clicked and the method upvote(news) is called, changes on this.votingList[news._id] takes place and the value is altered as shown below:
async upvote(news) {
axios.post(serverSide.findUserByID, {userID: this.user._id})
.then((res) => {
this.user.rep = res.data.user.rep
if (this.votingList[news._id].upvoted == true) {
alert('You have already voted!')
return
}
else {
axios.post(serverSide.castVote, {
// function params
})
.then(() => {
this.votingList[news._id].upvoted = true
alert("Vote Casted")
console.log("Voting List: ", this.votingList[news._id])
this.upvoteVariant(news)
return
})
}
})
},
So why isn't b-icon's variant changing even though the data has changed?

Assuming upvoted and downvoted were not initially in the news object (they were inserted by upvote() and downvote(), respectively), Vue 2 cannot detect addition/deletion of properties in objects.
As a workaround, you could use this.$set():
export default {
methods: {
async upvote(news) {
const item = this.votingList[news._id]
if (item.upvoted || item.downvoted) {
alert('You have already voted!')
return
}
this.$set(item, 'upvoted', true) 👈
},
}
}
demo

Related

I want handle the delete function by vuejs

This is my Indexing.vue component.
<div>
<div v-for="data in indexingList" :key="data.indexing_id">
<p>{{ data.indexing_name }}</p>
<p>{{ data.indexing_url }}</p>
</div>
<base-button type="primary" size="sm" #click="deleteIndexing(data.indexing_id)">Delete
</base-button>
</div>
export default {
data() {
return {
indexingList: [],
}
},
methods: {
getIndexingList: function() {
this.api.getDataModule("indexing/" + store.state.journalId, "journals/v1/")
.then((res) => {
console.log(res.data);
this.indexingList = res.data.indexingList;
},
(err) => {
console.log(err);
}
);
},
deleteIndexing: function(dVal) {
let sVal = {};
sVal.indexing_id = dVal;
this.api.putData("indexing/" + store.state.journalId + "/deleteindexing", sVal)
.then((res) => {
console.log(res.data);
},
(error) => {
console.log(error);
}
);
},
},
mounted: function() {
this.getIndexingList();
},
}
I'm getting data from the server using getIndexingList function in the form of the API get method. And removing the data once the user clicks the delete button by using the deleteIndexing function in the form of the put API method.
We wrote APIs in a separate file. Here in indexing.vue we are just passing the APIs.
Now I want to fix the delete(remove) function. The data was removed from the database once the user clicks the delete button but it is not removed from the webpage. Every time I need to refresh the page to see the changes.
You can use Vue.delete (this.$delete) which also ensure that the deletion triggers the view updates.
Try this-
deleteIndexing: function(dVal) {
let sVal = {};
sVal.indexing_id = dVal;
this.api
.putData("indexing/" + store.state.journalId + "/deleteindexing", sVal)
.then(
(res) => {
// find the item from indexingList and remove it
// Also make sure your response has the id of the deleted item
let index = this.indexingList.findIndex(item => item.id == res.id);
if (index != -1) {
this.$delete(this.indexingList, index);
}
},
(error) => {
console.log(error);
},
);
},

How to Open MODAL after its been added to DOM - vue 3 bootstrap 5 nodejs

I dont know much about vue/bootstrap and reading docs does not help me to understand how it all works.
How to open a modal that is created after the page was loaded. From user input. User clicks button then the modal loads into a list prop and then renders into DOM and then it opens up.
Im at the point where i created event when user clicks the button that loads the modal into the list, but how do you catch the "modal has been added to DOM" event and then you can use getElementByID to instantiate the modal and then use .show() to show it?
I can see that the card that supposed to render on the page loads/renders, but the method get null. Im guessing that the method runs before the page/DOM has been re-rendered. So how do you run another method with parameter of sorts after the custom event that added the item to list has been triggered?
The code is too big and convoluted to post. But if need be i could try to trim it down, but its a mess.
App.vue
<template>
<div class="container-center">
<AnimeList />
</div>
</template>
AnimeList.vue
<template>
<div class="containerlist">
<AnimeCardModal
v-for="anime in animeList"
:anime="anime"
#checkAnimeListForRelatedEvent="checkAnimeListForRelated"
/>
</div>
</template>
<script setup>
import { defineComponent } from "vue";
import AnimeCardModal from "./AnimeCardModal.vue";
import axios from "axios";
</script>
<script>
export default defineComponent({
name: "AnimeList",
data() {
return {
animeList: [],
limit: 30,
page: 1,
reachedEnd: false,
};
},
methods: {
async getAnimeLsit() {
const res = await axios.get("/api", {
params: { page: this.page, limit: this.limit },
});
this.animeList = res.data.data;
this.page = res.data.next.page;
this.limit = res.data.next.limit;
},
async getNextBatch() {
let bottomOfWindow =
document.documentElement.scrollTop + window.innerHeight ===
document.documentElement.offsetHeight;
if (bottomOfWindow && !this.reachedEnd) {
const res = await axios.get("/api", {
params: { page: this.page, limit: this.limit },
});
res.data.data.map((item) => {
this.animeList.push(item);
});
if (!res.data.next) {
this.reachedEnd = true;
} else {
this.page = res.data.next.page;
this.limit = res.data.next.limit;
}
}
},
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]);
console.log("added to list");
}
}
// Add the anime to the list
},
},
created() {
window.addEventListener("scroll", this.getNextBatch);
},
deactivated() {
window.removeEventListener("scroll", this.getNextBatch);
},
async mounted() {
await this.getAnimeLsit();
},
components: {
AnimeCardModal,
},
});
</script>
Here is the method that gets triggered by the user click event where it loads the Not in main list data and should render on page/DOM.
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]); <--------------------------------------
console.log("added to list");
}
}
// Add the anime to the list
},
The added item is a modal with element id. I want to instantiate this element as new Modal() and open it with .show().
But the i get error that the element does not exist = null and i cant get it, but i can see it on screen.
EDIT:1
Ok so like as per usual, once i post on SO i find an answer to my problem, but it turns into another problem.
SO to get the rendered element i used this:
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]);
console.log("added to list");
this.$parent.$nextTick(() => { <----------------------
const myModal = new Modal(
document.getElementById("anime-card-modal-" + animeID)
);
myModal.show();
}
}else{
const myModal = new Modal(
document.getElementById("anime-card-modal-" + animeID)
);
myModal.show();
}
// Add the anime to the list
},
It works, but now the modals overlay each other, seems like its not working like when you add the attributes to the card element that opens modal:
:data-bs-target="'#anime-card-modal-' + anime.id"
data-bs-toggle="modal"
Is there a way to get the same effect from method as with these attributes?
I want to open a modal, by clicking an element with those attributes, then when i click another element with them attributes (different target id) it closes previously opened modal and opens the target modal.
Alright, i found a solution, works pretty good.
Instead of using myModal.show() i used myModal.toggle("anime-card-modal-" + animeID) and the else statement is not needed in the event method:
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]);
console.log("added to list");
this.$parent.$nextTick(() => {
const myModal = new Modal(
document.getElementById("anime-card-modal-" + animeID)
);
myModal.toggle("anime-card-modal-" + animeID) <---------------
}
}
// Add the anime to the list
},

How to make redux function call on radio button click react native?

I am working on project of Food app. I have preference screen where user can click on any desired preference to add in order. This is working but I want to dispatch a action so the clicked data is stored inside of redux store.
User can only select few from given like 2 or 3.
This is the function i am using for only few item slection logic
{item.map((items, index) => (
<TouchableOpacity
key={items.id}
onPress={() => {
const id=preferenceData.indexOf(item)
//console.log("id=",id)
// console.log("itemsID",items.checked)
if (minimum_quatity[id] !== null) {
//console.log(" 1st loop enter")
preferenceData[id].forEach((i) => {
const check=item.filter(items =>items.checked ? items : null)
if (i.id == items.id) {
// console.log("checked",check)
// console.log("condition",(check.length < minimum_quatity[id]))
if (check.length < minimum_quatity[id]) {
console.log("before" ,i.checked)
i.checked=!i.checked;
console.log("after", i.checked)
selectItem(i, i.checked, ranid,i.name)
}
else {
console.log("falsed")
i.checked = false;
selectItem(i, i.checked, ranid,i.name)
}
}
setpreferenceData([...preferenceData])
});
setcounter(counter[id] + 1);
console.log("itemsID",items.checked);
}
else {
preferenceData[id].forEach((i) => {
if (i.id === items.id) {
i.checked = !i.checked;
selectItem(i, i.checked, ranid,i.name)
console.log("null")
}
});
setpreferenceData([...preferenceData]);
}
}}
>
This runs perfectly fine when runs first time but if i diselect a radio button and again selects it then it data is never sent in redux SelectItem action .
This is action selectItem
const selectItem = (checkeditems, checkboxValue, id, name) => {
dispatch({
type: 'ADD_TO_CART',
payload: {
...checkeditems,
checkboxValue: checkboxValue,
id: id,
name: name,
},
});
};
My reducer file is fine because i have used it and it works fine.
I know that i should have used redux tool kit but just for now i am using it for learning purpose
switch (action.type) {
case 'ADD_TO_CART': {
let newState = { ...state };
if (action.payload.checkboxValue) {
console.log('ADD TO CART',action.payload);
newState.selectedItems = {
checkeditems: [
...newState.selectedItems.checkeditems,
action.payload,
],
items: [...newState.selectedItems.items],
quantity:[...newState.selectedItems.quantity]
};
console.log('ADD TO CART return');
} else {
console.log('REMOVE FROM CART');
newState.selectedItems = {
checkeditems: [
...newState.selectedItems.checkeditems.filter(
(item) => (item.name !== action.payload.name)
),
],
items: [...newState.selectedItems.items],
};
}
console.log(newState, '👉');
return newState;
}
The ouput is attached .I pressed two radio buttons and the deselected them and output is fine .It adds to cart and remove from cart.
but after it when there again selects any radio button it does nothing
Output image

How can I call multiple eventTypes in one function?

I have written the following function in my service:
public refresh(area: string) {
this.eventEmitter.emit({ area });
}
area accesses all my childs and should update them in the parent with a click.
// In Childs
this.myService.eventEmitter.subscribe((data) => {
if (!this.isLoading && data.area === 'childFirst') {
this.loadData();
}
});
this.myService.eventEmitter.subscribe((data) => {
if (!this.isLoading && data.area === 'childSecond') {
this.loadData();
}
});
this.myService.eventEmitter.subscribe((data) => {
if (!this.isLoading && data.area === 'childThird') {
this.loadData();
}
});
// My Parent-Component
// TS
showChildFirst() {
this.navService.sendNavEventUpdate('childFirst');
}
showChildSecond() {
this.navService.sendNavEventUpdate('childSecond');
}
showChildThird() {
this.navService.sendNavEventUpdate('childThird');
}
public refresh(area: string) {
this.myService.refresh(area);
}
// HTML
<!-- Refresh your childs -->
<button type="button" (click)="refresh()">Refresh</button>
If I insert the following into the function: refresh('childFirst') the first child component is updated. Is there a way to refresh all eventTypes in refresh?
You can change your 'refresh' method to get an array of strings instead of a single string. So the method would become
public refresh(areas: string[]) {
areas.forEach(area =>
this.myService.refresh(area);
)
}
and calling it would be
<button type="button" (click)="refresh(['childFirst','childSecond','childThird'])">Refresh</button>

Validations in Vue using Vuelidate are not working

Hey guys
I wonder if I miss something here, iv'e trying to figure it out for a few hours and didn't came up with a solution.
I'm trying to use form validations using Vuelidate in vue, everything seems in place but after the custom alert I created, the form is still proceeding to the next stage.
I declared Vuelidate like this:
import useVuelidate from '#vuelidate/core'
import { required } from '#vuelidate/validators'
Inside Data() I did as follows:
data: () => ({
v$: useVuelidate(),
currentStage: 1,
traffic: {
unique: '',
unique1: '',
unique2: '',
unique3: '',
unique4: ''
},
}),
Declared validations outside of data(), like this:
validations() {
return {
traffic: {
unique1: { required },
unique2: { required },
unique3: { required },
unique4: { required },
unique5: { required }
},
}
},
Last thing is the computed, which I have function that is creating the input fields in stage 3 of the form:
computed: {
appendFields() {
this.v$.$validate()
if(!this.v$.$error){
if(this.jsonStatham.hasOwnProperty(this.traffic.unique1)){
this.integrationParams.push(...Object.keys(this.jsonStatham[this.traffic.unique1]))
}
} else {
alert("Error, Not all fields are filled in.")
}
}
},
So here is the problem, when appendFields() called, I do get this alert: alert("Error, Not all fields are filled in.")
But After I press "ok" in the alert, the form is still proceeding to the next stage.
What am I missing?
Edit:
This is the button who execute the "appendFields" method:
<button #click="buttonClicked(0); appendFields;">Next Stage</button>
And this is buttonClicked function:
buttonClicked(value) {
if(value === 0){
this.currentStage++;
return this.currentStage;
}
if(value === 1){
this.currentStage--;
return this.currentStage;
}
},
The click-handler updates currentStage without first validating the form. However, the validation occurs in appendFields, which is computed after buttonClicked(). The validation should be the first step, which can block the proceeding steps.
I would refactor it like this:
Make appendFields a component method, since it's not really a computed property (especially because it returns nothing).
Move the currentStage update into its own function for clarity.
Move the form validation from appendFields() to the button's click-handler.
In the click-handler, call the functions created in step 1 and 2 if the form is valid.
export default {
methods: {
// 1️⃣
appendFields() {
if (this.jsonStatham.hasOwnProperty(this.traffic.unique1)) {
this.integrationParams.push(...Object.keys(this.jsonStatham[this.traffic.unique1]))
}
},
// 2️⃣
updateStage(value) {
if (value === 0) {
this.currentStage++
} else if (value === 1) {
this.currentStage--
}
},
buttonClicked(value) {
// 3️⃣
this.v$.$validate()
if (!this.v$.$error) {
// 4️⃣
this.appendFields()
this.updateStage(value)
} else {
alert("Error, Not all fields are filled in.")
}
}
}
}
Also be aware that useValidate() is intended for the Composition API, so it should be called inside setup():
export default {
setup() {
return {
v$: useValidate()
}
}
}

Categories