Vue & LoDash: Toggling Active Tab not working - vuejs2

I have an array of auctions with each element looking like this (significantly shortened for question purpose)
{
id: 1,
title: 'N782AM',
is_active: true
}
I have a button bar of buttons like this
<button
v-for="(tab, idx) in auctions"
class="button tab-button"
:key="tab.id"
:class="{ active: tab.is_active}"
#click="toggleTab(idx)">
{{ tab.title }}
</button>
The method toggleTab() is this
toggleTab(idx) {
_.forEach(this.auctions, (a) => a.is_active = false);
this.auctions[idx].is_active = true;
}
++++UPDATED++++
As suggested I changed toggleTab as below
toggleTab(idx) {
_.forEach(this.auctions, (a) => a.is_active = false);
Vue.set(this.auctions[idx].is_active, true);
}
this results in this error:
vue.common.js?2371:1037 Uncaught TypeError: Cannot use 'in' operator
to search for 'VN-A566' in N-782AM
++++UPDATED++++
My expectation is that when clicked, the clicked tab should be assigned the active class and turn blue and all other tabs should have the active class removed but nothing is happening.

This is a very common reactivity problem. using Vue.set would solve it.
Vue.set has 3 params. Vue.set( target, key, value )
This is an example of how to use it in the method
toggleTab(idx) {
_.forEach(this.auctions, (a, i) =>
this.$set(this.auctions[i], "is_active", false)
);
this.$set(this.auctions[idx], "is_active", true);
}
Learn more in https://v2.vuejs.org/v2/guide/reactivity.html

Related

Common onClick event listener for both button element and RouterLink component in Vue2

In below code, the component could be either button, input or RouterLink, and we don't know at advance which one will be. I added a lot of attributes to prevent v-if solution, because in this case we need to duplicate most of attributes:
<component
:is="rootElementTagOrComponentName"
#click.prevent="$emit('click', $event.target)"
:to="route"
:type="inputOrButtonElementTypeAttributeValue"
:class="CSS_Classes"
:value="rootElementTagNameIsInput && lettering"
:role="rootElementIsLink && 'link'"
:disabled="(rootElementTagNameIsButton || rootElementTagNameIsInput) && disabled"
:ref="ROOT_ELEMENT_REFERENCE"
>
<template v-if="!rootElementTagNameIsInput && lettering">{{ lettering }}</template>
<slot v-if="!rootElementTagNameIsInput"></slot>
</component>
The #click.prevent="$emit('click', $event.target)" not work for RoterLink. However, with #click.native.prevent="$emit('click', $event.target)" we get
[Vue warn]: The .native modifier for v-on is only valid on components but it was used on <button>.
How to resolve this conflict?
It gets tricky when you have a dynamic component which may or may not be a native element. Unfortunately there is no easy way to do this in the template alone, you'll have to do it in code (v-on object syntax does not support native events).
Untested, but something like this may work:
<Root
:to="route"
:type="inputOrButtonElementTypeAttributeValue"
:class="CSS_Classes"
:value="rootElementTagNameIsInput && lettering"
:role="rootElementIsLink && 'link'"
:disabled="(rootElementTagNameIsButton || rootElementTagNameIsInput) && disabled"
:ref="ROOT_ELEMENT_REFERENCE"
>
<template v-if="!rootElementTagNameIsInput && lettering">{{ lettering }}</template>
<slot v-if="!rootElementTagNameIsInput"></slot>
</Root>
components: {
Root: {
functional: true,
render(h, ctx) {
const { data, parent, children } = ctx
const tag = parent.rootElementTagOrComponentName
const click = e => {
e.preventDefault()
parent.$emit('click', e.target)
}
if (tag === 'RouterLink') {
data.nativeOn = data.nativeOn || {}
data.nativeOn.click = click
} else {
data.on = data.on || {}
data.on.click = click
}
return h(tag, data, children)
}
}
}
All I've done in code is deal with registering the click event, but you could move other things into code as well if you want.

How come is my array not reactive in vuejs?

Good evening everyone,
I have been making a kind of social network as a personal project using vuejs, nodejs and mysql database.
Basically, you can post a message, and then people can answer to it. I bind comments to post using an id. I got two tables: 1 comments and 1 posts. If a comment is posted for post number 38, in mysql table there is a field idPost = 38.
i got a function displaying all the answers for the post by clicking on a button, which is:
displayAnswers(id) {
axios.get('http://localhost:3000/wall/answer/get/'+id )
.then(response => {
this.answers = response.data.resultat;
})
.catch(error => {
console.log(error);
})
}
Where id is the id of the post I want to display answers.
Now, the problem is when I add a comment, I need either to refresh the page to see the comment or to force the refresh by calling the displaypost function, like this:
postAnswer(id) {
let syntaxe = /^[a-z A-ZáàâäãåçéèêëíìîïñóòôöõúùûüýÿæœÁÀÂÄÃÅÇÉÈÊËÍÌÎÏÑÓÒÔÖÕÚÙÛÜÝŸÆŒ0-9-]{1,}$/;
if(syntaxe.test(this.answerToPost)) {
let answer = {
message: this.answerToPost,
postId: id,
auteur: this.$store.state.pseudoUser
}
axios.post('http://localhost:3000/wall/post/answer', answer)
.then(response => {
console.log(response);
this.feedbackMessage = response.data.message;
this.isAlert = false;
this.answerToPost = '';
setTimeout(() => {
this.feedbackMessage = ''
}, 2000);
this.displayAnswers(id);
})
.catch(error => {
console.log(error);
this.feedbackMessage = error.response.data.message;
this.isAlert = true;
})
} else {
this.errorMessage = "Le message ne respecte pas la syntaxe autorisée";
return;
}
},
To summarize, my data this.answers, is not reactive. it is declared this way in the app:
data() {
return {
Auteur: '',
displayPostAnswers: [],
answerToPost: '',
isAlert: true,
feedbackMessage: '',
answers: ''
}
},
and called this way in my template, using a v-for loop to display the answers:
<div v-for="answer in answers" :key="answer.id" class="answerDisplayer" >
<div class="containerEachAnswer">
<div class="avatarAuteur">
<img src="../assets/defaultUser.png" width="48" height="48" alt="">
</div>
<div class="answer">
<span>
<strong>{{ answer.auteur }}</strong><br>
{{ answer.message}}
</span>
</div>
</div>
</div>
</div>
I looked for the issue on the internet, I found this doc: https://fr.vuejs.org/v2/guide/reactivity.html.
So I tried to use the function Vue.set but it does not seem to work.
I would like to know if more experienced developer could help me to find another way to either make my data reactive or help me to do it another way, I tried several kind of things but it did not work.
PS: I tried to use computed data, but v-for does not work with computed data.
Thank you!
Have a good evening!
Since you are trying to change this within the instance I suggest you try this.$set(this.someObject, 'b', 2) as described in https://fr.vuejs.org/v2/guide/reactivity.html#Pour-les-objects
Also you seem to declare answers as a string in your data function, try declaring it as an array answers: []

vue element checkbox not working in edit mode

I am using checkbox given by vue-element, visit https://element.eleme.io/#/en-US/component/checkbox.
But in edit mode they are not able to select anymore.
Please help me out.
<el-checkbox-group v-model="form.activity_type" size="small">
<el-checkbox-button
v-for="(all_type,index) in all_activity"
:key="index"
:label="all_type.id"
>{{all_type.activity_type_name}}</el-checkbox-button>
</el-checkbox-group>
<el-checkbox-group v-model="form.activity_type" size="small">
<e`enter code here`l-checkbox-button
v-for="(all_type,index) in all_activity"
:key="index"
:label="all_type.id">
{{all_type.activity_type_name}}
</el-checkbox-button>
</el-checkbox-group>
Hi you in order to show checkbox in edit mode you can do it like this:
data() {
return {
form: {
activity_type: []
}
};
}
import {activityData} from "#/api/activity";
2)under method function:
activityData(id).then(response => {
this.form = response.res;
});
3) and then from your controller function you can pass data in this format:
$allData = [];
$allData['activity_type'] = array(1,3);//the ids one you want to show check
return response()->json(["res" => $allData]);

Vue + Vue-Paginate: Array will not refresh once empty

I am using vue-paginate in my app and I've noticed that once my array is empty, refreshing its value to an array with contents does not display.
<paginate
name="recipes"
:list="recipes"
:per="16"
class="p-0"
>
<transition-group name="zoom">
<div v-for="recipe in paginated('recipes')" :key="recipe.id">
<recipe class=""
:recipe="recipe"
:ref="recipe.id"
></recipe>
</div>
</transition-group>
</paginate>
This is how things get displayed, and my recipe array changes depending on a search. If I type in "b" into my search, results for banana, and bbq would show. If I typed "ba" the result for bbq is removed, and once I backspace the search to "b" it would re-appear as expected.
If I type "bx" every result is removed and when I backspace the search to "b", no results re-appear.
Any idea why this might happen?
UPDATE
When I inspect the component in chrome I see:
currentPage:-1
pageItemsCount:"-15-0 of 222"
Even though the list prop is:
list:Array[222]
Paginate needs a key in order to know when to re-render after the collection it's looking at reaches a length of zero. If you add a key to the paginate element, things should function as expected.
<paginate
name="recipes"
:list="recipes"
:per="16"
class="p-0"
:key="recipes ? recipes.length : 0" // You need some key that will update when the filtered result updates
>
See "Filtering the paginated list" is not working on vue-paginate node for a slightly more in depth answer.
I found a hacky workaround that fixed it for my app. First, I added a ref to my <paginate></paginate> component ref="paginator". Then I created a computed property:
emptyArray () {
return store.state.recipes.length == 0
}
then I created a watcher that looks for a change from length == 0 to length != 0:
watch: {
emptyArray: function(newVal, oldVal) {
if ( newVal === false && oldVal === true ) {
setTimeout(() => {
if (this.$refs.paginator) {
this.$refs.paginator.goToPage(page)
}
}, 100)
}
}
}
The timeout was necessary otherwise the component thought there was no page 1.
Using :key in the element has certain bugs. It will not work properly if you have multiple search on the table. In that case input will lose focus by typing single character. Here is the better alternative:
computed:{
searchFilter() {
if(this.search){
//Your Search condition
}
}
},
watch:{
searchFilter(newVal,oldVal){
if ( newVal.length !==0 && oldVal.length ===0 ) {
setTimeout(() => {
if (this.$refs.paginator) {
this.$refs.paginator[0].goToPage(1)
}
}, 50)
}
}
},

Toggle detailed row in buefy table

I have a buefy table with details. Whenever I click on a chevron, the detailed view of the according row shows. It would be much better in my case, to have only one detailed view open. The desired outcome is: Whenever I click on a chevron, that detailed view opens and all other close.
In buefy, the opening of the detailed view is programmed like this:
<td v-if="detailed">
<a role="button" #click.stop="toggleDetails(row)">
<b-icon
icon="chevron-right"
both
:class="{'is-expanded': isVisibleDetailRow(row)}"/>
</a>
</td>
...
props: {
...
detailed: Boolean
...
}
...
methods: {
...
toggleDetails(obj) {
const found = this.isVisibleDetailRow(obj)
if (found) {
this.closeDetailRow(obj)
this.$emit('details-close', obj)
} else {
this.openDetailRow(obj)
this.$emit('details-open', obj)
}
// Syncs the detailed rows with the parent component
this.$emit('update:openedDetailed', this.visibleDetailRows)
},
openDetailRow(obj) {
const index = this.handleDetailKey(obj)
this.visibleDetailRows.push(index)
},
closeDetailRow(obj) {
const index = this.handleDetailKey(obj)
const i = this.visibleDetailRows.indexOf(index)
this.visibleDetailRows.splice(i, 1)
},
isVisibleDetailRow(obj) {
const index = this.handleDetailKey(obj)
const result = this.visibleDetailRows.indexOf(index) >= 0
return result
},
...
}
I see that there is an update_event sent to the parent. Do I have to save the
visibleDetailRows and tell the Child component to close it, when the button is pressed again? How would I do that?
The way I did it was to use the #details-open event to call a custom function :
<b-table
:data="data"
:opened-detailed="defaultOpenedDetails"
detailed
detail-key="id"
#details-open="(row, index) => closeAllOtherDetails(row, index)"
>
When you expand a row, the event is triggered.
And the called function closeAllOtherDetails() deletes all elements of the defaultOpenedDetails array, except the current row you just expanded :
closeAllOtherDetails(row, index) {
this.defaultOpenedDetails = [row.id]
}
That does the trick. Simple!