I have a component with buttons that show some elements (v-autocomplete) when we click on them, but I should make it with a Boolean to simplify the code, how can I do that?
Because actually, it adds the index of the item in one array in showCompetence state, but I just would like a Boolean on each index Basically at the "openCompetence" function in "Methods".
import { mapGetters, mapActions } from "vuex";
export default {
name: "SkillCvCard",
data() {
return {
selectedCompetence: []
}
},
updated() {
this.addSelectSkill(this.userCompetences.competences.list);
console.log(this.selectedSkills)
},
props: {
userCompetences: {
type: Array
},
showCompetence: {
type: Array
}
},
computed: {
...mapGetters(["selectedSkills"]),
console: () => console,
},
methods: {
...mapActions(['addSelectSkill']),
openCompetence(index) {
if (this.showCompetence.includes(index)) {
console.log("close")
this.showCompetence.splice(this.showCompetence.indexOf(index), 1)
} else {
this.showCompetence.push(index)
console.log("open")
}
console.log(this.showCompetence)
}
}
about the template I will just add the minimum I hope it will be ok:
The first is where we click, to launch the "openCompetence" function.
<div v-for="(competences, index) of userCompetences.competences" :key="index">
{{ competences.category }} <v-btn #click="openCompetence(index)"> Add </v-btn>
</div>
the rest is a v-container with a v-for including multiple v-autocomplete, but the most important inside is the v-if:
<div class="skill-field" v-for="(skill, index) of userCompetences.competences" :key="index">
<template>
<v-flex md12 sm12 xs12 v-if="skill.list.length>0">
<v-autocomplete
v-if="showCompetence.includes(index)"
v-model="userCompetences.competences.list"
:items="skill.list"
chips
hide-selected
:label="skill.category"
item-text="name"
item-value="name"
multiple
>
</v-autocomplete>
</v-flex>
</template>
</div>
Normally, you would use a computed property to solve a problem like this. However, because you are inside of a v-for, a computed property is a little more difficult since you can't evaluate it in the context of each value of the v-for's index.
There are two common options:
Do exactly what you are doing. It's actually no less efficient since it will not re-evaluate unless data it depends on changes.
Add a computed called something like competencesDisplayState that returns an array of booleans that matches the ordering of the userCompetences.competences array in the v-for. Then your v-if can become something like:
v-if="competencesDisplayState[index]"
I normally just opt for the first approach as it is simpler and is easier to read and maintain.
If you want to go route 2, here's some code for the competencesDisplayState computed:
competencesDisplayState: () => {
var result = [];
for (var index=0;index < this.userCompetences.competences.length;++index) {
result(this.showCompetence.includes(index));
}
return result;
}
Related
I'm working currently with BootstrapVue.
I have a b-dropdown in my parent.vue where I can select a object of a JSON-File and convert it into an array because I need the length of this json object. This works fine!!
My problem is that I need to check in my parent.vue if something was selected - so if this.arrayLength is higher than 0 (until this point it works all well!). If this is true, it should use and show addElementsNotClickable() in my child.vue where no elements can be added (count of the inputs are equal to length of array) - otherwise it should use and show my button addElement() where multiple elements can be added manually.
But I'm not able to check in my child.vue if arrayLenght > 0... AND i don't know what to use on second button e.g #change(??) How can I solve that?
Many thanks! I've tried to be as detailed as I can!
Additional Info: I get no error codes!!
my parent.vue:
methods: {
inputedValue(input, index) {
var array = [];
const item= this.json.find((i) => i.Number === input);
for (let key in item.ID) {
array.push(item.ID[key]);
}
if(array.length > 0) {
this.getIndex = index;
this.getDataArray = array;
this.getLengthArray = array.length;
}
}
}
my child.vue (template)
<div class="mt-4 mb-5 ml-3 mr-3">
<b-button v-if="!hide" #click="addElement" variant="block">Add Element</b-button>
<b-button v-if="hide" #???="addElementNotClickable" variant="block">Not clickable ! </b-button>
</div>
my child.vue (script)
methods: {
addElementsNotClickable() {
for(let i = 1; i < this.arrayLength; i++) {
this.inputs.push({})
}
},
addElement() {
this.inputs.push({})
},
}
data() {
return {
inputs: [{}]
arrayLength: this.getLengthArray,
arrayIndex: this.getIndex,
hide: false,
}
props: [
"getLengthArray",
"getIndex"
],
You are misunderstanding how components should work in Vue. In short you can understand them by:
parent send props down and child send events up
What you are looking for is that whenever your arrayLength updates, you send an event to the parent. Then, it is the responsibility of the parent to handle that event. In this case, the parent would receive the event and store the length of the array.
Parent.vue
<template>
<div>
<child #arrayLenght:update="onArrayLenghtUpdate"/>
</div>
</template>
<script>
export default {
data: () => {
arrayLength: 0,
},
methods: {
onArrayLenghtUpdate(length) {
this.arrayLength = length;
}
}
}
</script>
Child.vue
<template> ... </template>
<script>
export default {
data: () => ({
arrayLength: 0,
}),
watch: {
arrayLenghth: function (newLenght) {
this.$emit('arrayLenght:update', newLenght);
}
}
}
</script>
This is the standard way and extremely useful if your Parent and Child aren't highly coupled together. If they are dependent on each other (you won't use Child.vue anywhere else in the app. Just as direct child of Parent.vue) then you can use the inject/provide approach. This approach is beyond the scope of this answer, feel free to read an article on it
I try to use a v-select input that uses the data I receive from my Vuex store. I have to use a computed property, since my data gets passed from an API.
My template looks like this.
<template>
<v-card v-if="isMounted">
<v-select
v-model="chartData.selected"
:items="chartData.items"
label="Select Item"
multiple
>
</v-select>
{{chartData.selected}}
</v-card>
</template>
<script>
import {mapState} from "vuex"
export default {
data: function () {
return {
isMounted: false,
value: []
}
},
computed: {
...mapState(["clusterValues"]),
chartData() {
return {
items: this.clusterValues.data,
selected: this.clusterValues.data,
}
}
}
}
</script>
I binded this computed property to v-model. However it does not update accordingly. I guess it does not work out with a computed property. (Vue.js - Input, v-model and computed property)
v-model="value". This works, but I it does not allow me to start with every item selected.
Starting like this does not work out: value: this.$store.state.clusterValues
How can I solve this problem?
When I try to run the following code, and I remove one item from the array, the item is not removed completely(there are other checkboxes part of each row which are not removed) I have added a :key="index" and doesn't help it.
Nevertheless when I have changed the :key="index" to :key="item" it works, but then the problem is I get the warning [Vue warn]: Avoid using non-primitive value as key, use string/number value instead
<template>
<div>
<filters-list-item v-for="(item, index) in items" :key="index" v-on:deleteItem="deleteItem(index)" :items="items" :item="item" :filterButtonSetting="filterButtonSetting" class="pb-3 pt-3 space-line"></m-filters-list-item>
<div class="pt-3">
<button class="btn" #click="add()">
add
</button>
</div>
</div>
</template>
<script>
import FiltersListItem from './FiltersListItem';
export default {
name: 'FiltersList',
components: {
FiltersListItem
},
props: {
items: Array,
filterButtonSetting: Object
},
methods: {
add() {
this.items.push({});
},
deleteItem(index) {
this.$delete(this.items, index);
},
}
};
Using the index is fine as long as you are not interacting with any of the elements in the loop.
But, if you are, then it is recommended not to do this.
You should use another unique item's identifier, maybe providing one from the backend.
I came across a Vuetify example for the v-dialog component which has the scoped slot called activator defined as follows:
<template v-slot:activator="{ on }">
<v-btn
color="red lighten-2"
dark
v-on="on"
>
Click Me
</v-btn>
</template>
I understand the purpose of scoped slots from VueJS docs and the concept of destructuring slot props but I don't understand what the meaning of v-on="on" is in this example. In particular what it means when the event is not specified with the v-on directive?
The VueJS docs on v-on only show its usage in combination with an event name explicitly specified (eg. v-on:click="...") but there is no explanation of just using it as v-on="...".
Can someone explain this syntax and its usage in the Vuetify example?
TLDR:
basic usage
<!-- object syntax (2.4.0+) -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>]
So basically #click="..." equals v-on:click="..." equals v-on="{click:...}"
TLDR:
vuetify implementation:
genActivator () {
const node = getSlot(this, 'activator', Object.assign(this.getValueProxy(), {
on: this.genActivatorListeners(),
attrs: this.genActivatorAttributes(),
})) || []
this.activatorNode = node
return node
}
Some insight:
It is useful if you want to abstract components and pass down multiple listeners at once instead of writing multiple lines of assignments.
Consider a component:
export default {
data() {
return {
on: {
click: console.log,
contextmenu: console.log
},
value: "any key value pair"
}
}
}
<template>
<div>
<slot name="activator" :on="on" :otherSlotPropName="value" >
<defaultComponent v-on="on" />
</slot>
</div>
</template>
Given the component above, you can access the slot properties and pass them into your custom component:
<ExampleComponent>
<template v-slot:activator="{ on, otherSlotPropName }">
<v-btn
color="red lighten-2"
dark
v-on="on"
>
Click Me
</v-btn>
</template>
<ExampleComponent />
Somethimes its easier to see it in plain javascript:
Comparing the component from above - with render function instead of template:
export default {
data() {
return {
on: {
click: console.log,
contextmenu: console.log
},
value: "any key value pair"
}
},
render(h){
return h('div', [
this.$scopedSlots.activator &&
this.$scopedSlots.activator({
on: this.on,
otherSlotPropName: this.value
})
|| h('defaultComponent', {
listeners: this.on
}
]
}
}
In the source:
In case of a blank v-on="eventsObject" the method bindObjectListener will be called resulting in the assignment of the events to data.on.
This happens in the createComponent scope.
Finaly the listeners are passed as VNodeComponentOptions and updated by updateListeners.
Where Vue extends - the Vuetify implementation inspected:
When taking into account that one can join and extend vue instances, one can convince himself that any component can be reduced to a more atomic version.
This is what vuetify utilizes in the e.g. v-dialog component by creating a activator mixin.
For now one can trace down the content of on mounted by the activatable:
const simplyfiedActivable = {
mounted(){
this.activatorElement = this.getActivator()
},
watch{
activatorElement(){
// if is el?
this.addActivatorEvents()
}
},
methods: {
addActivatorEvents(){
this.listeners = this.genActivatorListeners()
},
genActivatorListeners(){
return {
click: ...,
mouseenter: ...,
mouseleave: ...,
}
},
genActivator () {
const node = getSlot(this, 'activator', Object.assign(this.getValueProxy(), {
on: this.genActivatorListeners(),
attrs: this.genActivatorAttributes(),
})) || []
this.activatorNode = node
return node
},
}
}
With above snippet all there is left is to implement this into the actual component:
// vuetify usage/implemention of mixins
const baseMixins = mixins(
Activatable,
...other
)
const sympliefiedDialog = baseMixins.extend({
...options,
render(h){
const children = []
children.push(this.genActivator())
return h(root, ...options, children)
}
})
I am quite new with VueJS and I have been having trouble lately with some computed properties which do not update as I would like. I've done quite some research on Stack Overflow, Vue documentation and other ressources but i haven't found any solution yet.
The "app" is basic. I've got a parent component (Laundry) which has 3 child components (LaundryMachine). The idea is to have for each machine a button which displays its availability and updates the latter when clicked on.
In order to store the availability of all machines, I have a data in the parent component (availabilities) which is an array of booleans. Each element corresponds to a machine's availability.
When I click on the button, I know the array availibities updates correctly thanks to the console.log. However, for each machine, the computed property "available" does not update is I would want it to and I have no clue why.
Here is the code
Parent component:
<div id="machines">
<laundry-machine
name="AA"
v-bind:machineNum="0"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
<laundry-machine
name="BB"
v-bind:machineNum="1"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
<laundry-machine
name="CC"
v-bind:machineNum="2"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
</div>
</div>
</template>
<script>
import LaundryMachine from './LaundryMachine.vue';
export default {
name: 'Laundry',
components: {
'laundry-machine': LaundryMachine
},
data: function() {
return {
availabilities: [true, true, true]
};
},
methods: {
editAvailabilities(index) {
this.availabilities[index] = !this.availabilities[index];
console.log(this.availabilities);
}
}
};
</script>
Child component:
<template>
<div class="about">
<h2>{{ name }}</h2>
<img src="../assets/washing_machine.png" /><br />
<v-btn color="primary" v-on:click="changeAvailability">
{{ this.availability }}</v-btn>
</div>
</template>
<script>
export default {
name: 'LaundryMachine',
props: {
name: String,
machineNum: Number,
availableArray: Array
},
methods: {
changeAvailability: function(event) {
this.$emit('change-avlb', this.machineNum);
console.log(this.availableArray);
console.log('available' + this.available);
}
},
computed: {
available: function() {
return this.availableArray[this.machineNum];
},
availability: function() {
if (this.available) {
return 'disponible';
} else {
return 'indisponible';
}
}
}
};
</script>
Anyway, thanks in advance !
Your problem comes not from the computed properties in the children, rather from the editAvailabilities method in the parent.
The problem is this line in particular:
this.availabilities[index] = !this.availabilities[index];
As you can read here, Vue has problems tracking changes when you modify an array by index.
Instead, you should do:
this.$set(this.availabilities, index, !this.availabilities[index]);
To switch the value at that index and let Vue track that change.