Vue 2 - change data value of all components - vue.js

I have following setup:
Vue code:
Vue.component('ordering-filters', {
template: `
<a href="#"
:class="iconClass + faClass"
aria-hidden="true"
#click="orderCountries({orderBy, order})">
</a>`,
props: {
orderBy: {
type: String,
required: true
},
order: {
type: String,
required: true
},
iconClass: {
type: String,
default: "fa fa-lg text-muted"
},
faClass: {
type: String,
required: true
}
},
methods: {
orderCountries(params){
Event.$emit('ordering-filters', params);
}
},
data() {
return {
isActive: false
}
}
});
HTML code:
<tr>
<td class="col-md-6">Country Name
<div class="arrow-group">
<ordering-filters
order-by="name"
order="asc"
fa-class=" fa-sort-asc"
></ordering-filters>
<ordering-filters
order-by="name"
order="desc"
fa-class=" fa-sort-desc"
></ordering-filters>
</div>
</td>
<td class="countries-visible-filter col-md-3">
<visible-filters></visible-filters>
</td>
<td>Order
<div class="arrow-group">
<ordering-filters
order-by="order"
order="asc"
fa-class=" fa-sort-asc"
></ordering-filters>
<ordering-filters
order-by="order"
order="desc"
fa-class=" fa-sort-desc"
></ordering-filters>
</div>
</td>
<td>Actions</td>
</tr>
I want to change isActive to all the ordering-filters components when click event is fired, set it to false for all and then set it to true on the clicked element. Is this possible?

You must delegate the control of the active element to the parent somehow. Here is one of the many ways to do that, using v-model (which is just a shortcut for :value and #input), and a simple computed prop.
Vue.component('ordering-filters', {
template: `<a href="#" #click="orderCountries()">
{{ filterId }}
<template v-if="isActive"> I'm on. </template>
</a>`,
props: ['value', 'filterId'],
computed: {
isActive() {
return this.value === this.filterId;
}
},
methods: {
orderCountries() {
// Do some ordering stuff
this.$emit('input', this.filterId); // The active element is now this one
}
}
});
new Vue({
el: '#app',
data() {
return {
activeFilterId: null
};
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<div id="app">
<ordering-filters filter-id="one" v-model="activeFilterId"></ordering-filters> -
<ordering-filters filter-id="two" v-model="activeFilterId"></ordering-filters> -
<ordering-filters filter-id="three" v-model="activeFilterId"></ordering-filters>
</div>

Related

How to make property in Vue component to be reactive

I'm trying to create a new component:
Vue.component('my-component', {
props: {
displayed: {
type: Boolean
}
},
template: `
<div v-bind:class="{'modal': true, 'auth-required': true, 'show-modal': displayed }">
<div class="modal__content">
<img src="/img/popup/close.svg" v-on:click="displayed = false;" alt="close" class="modal__closeBtn modal__closeBtn-questions" />
<slot></slot>
<img src="/img/popup/dog.png" alt="dog" class="modal__contentImg" />
</div>
</div>`,
data: function () {
return {
isDisplayed: this.displayed
};
},
})
But when i'm trying to bind displayed property to another property from the page it doesn't work when modal.authRequired value changes:
<mycomponent :displayed="modal.authRequired"></mycomponent>
How to make isDisplayed to be reactive when modal.authRequired changes
Add a watch to your prop:
Vue.component('my-component', {
props: {
displayed: {
type: Boolean
}
},
template: `
<div v-bind:class="{'modal': true, 'auth-required': true, 'show-modal': isDisplayed }">
<div class="modal__content">
<img src="/img/popup/close.svg" v-on:click="displayed = false;" alt="close" class="modal__closeBtn modal__closeBtn-questions" />
<slot></slot>
<img src="/img/popup/dog.png" alt="dog" class="modal__contentImg" />
</div>
</div>`,
data: function () {
return {
isDisplayed: this.displayed
};
},
watch: {
displayed(newValue) {
// Update the data value
this.isDisplayed = newValue;
}
}
})
Also, notice that I changed the 'show-modal' binding: 'show-modal': isDisplayed
Reactive properties are the computed one :https://v2.vuejs.org/v2/guide/computed.html

How to close a dropdown list when clicking anywhere

How do I close the dropdown that opens when I click anywhere?
This code:
<tr class="inputs-table">
<td>Type object: </td>
<td>
<div class="select">
<div class="select-header form-control" v-on:click="AddForm(1)">
<span class="select__current">Please select one option</span>
</div>
<addForm v-if="addedForm === 1" />
</div>
</td>
</tr>
<tr class="inputs-table">
<td>Type business-model: </td>
<td>
<div class="select">
<div class="select-header form-control" v-on:click="AddForm(2)">
<span class="select__current">Please select one option</span>
</div>
<addForm v-if="addedForm === 2" />
</div>
</td>
</tr>
export default {
name: 'Index',
data() {
return {
addedForm: 0,
}
},
methods: {
AddForm(number) {
this.addedForm = number;
},
closeForm() {
this.addedForm = false;
}
},
components: {
addForm,
}
}
Drop-list:
What can I try next?
You can make use if window.onClick and see whether or not it matches you'r element. If it doesn't then close it; call the function that checks clicks at mounted and beforeDestroy or breforeRouteLeave.
E.g:
mounted(){
this.hideNav()
},
methods: {
hideNav() {
window.onclick = function(event) {
if (!event.target.matches('.select__body')) {
this.closeForm()
}
}
}
},
beforeRouteLeave() {
this.hideNav()
}
<template>
<div
#click="dropdownIsActive = !dropdownIsActive"
ref="dropdown"
>
</div>
</template>
<script>
export default {
data: () => ({
dropdownIsActive: false
}),
created () {
window.addEventListener('click', this.closeDropdown)
},
methods: {
closeDropdown (e) {
if (!this.$refs.dropdown.contains(e.target)) {
this.dropdownIsActive = false
}
}
},
beforeDestroy () {
window.removeEventListener('click', this.closeDropdown)
}
}
</script>

changing a single value using v-model / full table is redrawn

I was building an editable table, which began to crawl to a halt when the number of rows started to run in the 100's. This led me to investigate what was going on.
In the example below, when changing the value in the input, the whole table is redrawn, and the ifFunction() function is trigged 4 times.
Why is this happening? Shouldn't Vue be capable of just redrawing the respective cell? Have I done something wrong with the key-binding?
<template>
<div id="app">
<table border="1" cellpadding="10">
<tr v-for="(row, rowKey) in locations" :key="`row_+${rowKey}`">
<td v-for="(column, columnKey) in row" :key="`row_+${rowKey}+column_+${columnKey}`">
<span v-if="ifFunction()">{{ column }}</span>
</td>
</tr>
</table>
<input v-model="locations[0][1]">
</div>
</template>
<script>
export default {
data() {
return {
locations: [
["1","John"],
["2","Jake"]
], // TODO : locations is not generic enough.
}
},
methods: {
ifFunction() {
console.log('ifFunction');
return true;
},
}
}
</script>
The data property defines reactive elements - if you change one part of it, everything that's depending on that piece of data will be recalculated.
You can use computed properties to "cache" values, and only update those that really need updating.
I rebuilt your component so computed properties can be used throughout: created a cRow and a cCell component ("custom row" and "custom cell") and built back the table from these components. The row and the cell components each have a computed property that "proxies" the prop to the template - thus also caching it.
On first render you see the ifFunction() four times (this is the number of cells you have based on the data property in Vue instance), but if you change the value with the input field, you only see it once (for every update; you may have to click "Full page" to be able to update the value).
Vue.component('cCell', {
props: {
celldata: {
type: String,
required: true
},
isInput: {
type: Boolean,
required: true
},
coords: {
type: Array,
required: true
}
},
data() {
return {
normalCellData: ''
}
},
watch: {
normalCellData: {
handler: function(value) {
this.$emit('cellinput', {
coords: this.coords,
value
})
},
immediate: false
}
},
template: `<td v-if="ifFunction()"><span v-if="!isInput">{{normalCellData}}</span> <input v-else type="text" v-model="normalCellData" /></td>`,
methods: {
ifFunction() {
console.log('ifFunction');
return true;
},
},
mounted() {
this.normalCellData = this.celldata
}
})
Vue.component('cRow', {
props: {
rowdata: {
type: Array,
required: true
},
rownum: {
type: Number,
required: true
}
},
template: `
<tr>
<td
is="c-cell"
v-for="(item, i) in rowdata"
:celldata="item"
:is-input="!!(i % 2)"
:coords="[i, rownum]"
#cellinput="reemit"
></td>
</tr>`,
methods: {
reemit(data) {
this.$emit('cellinput', data)
}
}
})
new Vue({
el: "#app",
data: {
locations: [
["1", "John"],
["2", "Jake"]
], // TODO : locations is not generic enough.
},
methods: {
updateLocations({
coords,
value
}) {
// creating a copy of the locations data attribute
const loc = JSON.parse(JSON.stringify(this.locations))
loc[coords[1]][coords[0]] = value
// changing the whole locations data attribute to preserve
// reactivity
this.locations = loc
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table border="1" cellpadding="10">
<tbody>
<tr v-for="(row, i) in locations" is="c-row" :rowdata="row" :rownum="i" #cellinput="updateLocations"></tr>
</tbody>
</table>
<!-- <input v-model="locations[0][1]">
<input v-model="locations[1][1]">-->
{{locations}}
</div>

#click bound to each item within v-for loop executed many times when clicking

I have a #click bound to individual items within a v-for loop. In the resulting render, I should have one #click for each item, so clicking one item should trigger the function bound to the item once.
Yet it triggers it as many times as there are items. Why?
<ul>
<li :key="option.value" v-for="option in options">
<QuizCheckbox
#click.native="handleClick(option.value)"
>
{{ option.value }}
</QuizCheckbox>
</li>
</ul>
...
methods: {
handleClick(val) {
console.log(val);
},
EDIT:
If I replaced ... with a simple element, then clicking that doesn't trigger the problem. So it's the <QuizCheckbox> component who's the culprit. However, nothing in it seems to indicate what could cause the problem. Here's the content of QuizCheckbox.vue:
<template>
<div :class="['quiz-checkbox', {'quiz-checkbox--checked': shouldBeChecked}]">
<div :class="['form-checkbox']">
<label class="form-checkbox__label">
<slot/>
<input
:checked="shouldBeChecked"
:value="value"
#change="updateInput"
class="form-checkbox__input"
type="checkbox"
/>
<span class="form-checkbox__checkmark"></span>
</label>
</div>
</div>
</template>
<script>
export default {
model: {
prop: 'modelValue',
event: 'change'
},
props: {
value: {
type: String,
},
modelValue: {
type: [Boolean, Array],
default: false
}
},
computed: {
shouldBeChecked() {
if (this.modelValue instanceof Array) {
return this.modelValue.includes(this.value);
}
return this.modelValue;
}
},
created() {
if (!this.$slots.default) {
console.error('QuizCheckbox: requires label to be provided in the slot');
}
},
methods: {
updateInput(event) {
const isChecked = event.target.checked;
if (this.modelValue instanceof Array) {
const newValue = [...this.modelValue];
if (isChecked) {
newValue.push(this.value);
} else {
newValue.splice(newValue.indexOf(this.value), 1);
}
this.$emit('change', newValue);
} else {
this.$emit('change', isChecked);
}
}
}
};
</script>
The code you post seems fine. Although simplified here is the code you post running:
new Vue({
el: 'ul',
data: {
options: [
{
value: 'option1',
},
{
value: 'option2',
}
]
},
methods: {
handleClick(val) {
console.log(val);
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<ul>
<li :key="option.value" v-for="option in options">
<div
#click="handleClick(option.value)"
>
{{ option.value }}
</div>
</li>
</ul>
The problem must be elsewhere.

dynamically call an object property in a v-for loop

so i ran into a problem again,
i want to make a table component where you can send a array to the component, and it will render a table for you
we set it up like this
<template>
<section class="container">
<Apptable :search="true" :loader="true" title="User data" :data="users"/>
</section>
</template>
<script>
import Apptable from "~/components/table.vue";
export default {
components: {
Apptable
},
data() {
return {
users: [
{
id: 1,
name: "Lars",
Adres: "hondenstraat 21",
phone: "06555965"
},
{
id: 1,
name: "John",
Adres: "verwelstraat 35",
phone: "06555965"
}
]
};
}
};
</script>
i send data to the component and loop it from there like this
<template>
<section class="container">
<h2 v-if="title">{{title}}</h2>
<input v-if="search" class="search" placeholder="Search">
<button v-if="loader" class="update" #click="dialog = true">Update</button>
<table class="table">
<thead>
<tr class="tableheader">
<th v-for="(item, index) in Object.keys(data[0])" :key="index">{{item}}</th>
</tr>
</thead>
<tbody>
<tr class="userdata" v-for="(item, index) in data" :key="index">
<td v-for="(name, index) in Object.keys(data[index])" :key="index">{{//TODO: I WANT TO SELECT THE ITEM.DYNAMIC PROPERTY}}</td>
</tr>
</tbody>
</table>
<loader v-if="loader" :trigger="dialog"/>
</section>
</template>
<script>
import loader from "~/components/loader.vue";
export default {
components: {
loader
},
data() {
return {
dialog: false
};
},
watch: {
dialog(val) {
if (!val) return;
setTimeout(() => (this.dialog = false), 1500);
}
},
props: {
data: {
type: Array,
required: true
},
title: {
type: String,
required: false,
default: false
},
loader: {
type: Boolean,
required: false,
default: false
},
search: {
required: false,
type: Boolean,
default: true
}
}
};
</script>
so if you look at the table. were i left the todo, if i put in the {{item}} in the todo place. i will get this in my column
enter image description here
but i want to select the key of the object dynamically. but if i put {{item.name}} in the todo place it will not select the key dynamically.
so the point is that i want to dynamically call a property from the object in the v-for so the columns will get the data in the cells.
You should use item[name] instead of item.name
<tbody>
<tr class="userdata" v-for="(item, index) in data" :key="index">
<td v-for="(name, nIndex) in Object.keys(data[index])" :key="nIndex">
{{ item[name] }}
</td>
</tr>
</tbody>