How to hang click on only one element using v-for? - javascript

Hi I'm new to vue and I'm trying to complete one task. I have dynamic component toggles which I render with v-for. Can you suggest how can I pass an extra click to only one button (button 'border-left')? Desirable illustrative examples
<script>
import Vue from "vue";
import BorderLeftComonent from "./BorderLeftComonent.vue";
import TextBalloonComponent from "./TextBalloonComponent.vue";
import DashedComponent from "./DashedComponent.vue";
export default Vue.extend({
data() {
return {
component: "button[0].name",
color: "",
buttons: [
{
label: "A",
isActive: false,
type: "border-left",
name: "BorderLeftComonent",
},
{
label: "A",
isActive: false,
type: "text-balloon",
name: "TextBalloonComponent"
},
{
label: "A",
isActive: false,
type: "dashed",
name: "DashedComponent"
},
],
};
},
methods: {
toggleShowPopup() {
this.isOpen = !this.isOpen;
},
activeBtn(event, index) {
this.buttons[index].isActive = !this.buttons[index].isActive;
}
},
computed: {
currentComponent() {
return this.component;
},
cssVars() {
return {
'--border-left': this.color,
}
}
},
</script>
template is presented here
<template>
<div id="btn-box">
<button
v-for="(button, index) in buttons"
:key="index"
:class="button.isActive ? 'on' : 'off'"
#click="component = button.name, activeBtn($event, index)">
<div :class="`btn btn-${button.type}`">{{ button.label }}</div>
</button>
</div>
</template>
the method i need to pass to only one button
toggleShowPopup() {
this.isOpen = !this.isOpen;
}

You need conditional event binding. Try this:
<button
v-for="(button, index) in buttons"
:key="index"
:class="button.isActive ? 'on' : 'off'"
#click="component = button.name, activeBtn($event, index), button.type === 'border-left' && toggleShowPopup()">
<div :class="`btn btn-${button.type}`">{{ button.label }}</div>
</button>

This way might be a little more cleaned-up:
//import BorderLeftComonent from "./BorderLeftComonent.vue";
//import TextBalloonComponent from "./TextBalloonComponent.vue";
//import DashedComponent from "./DashedComponent.vue";
/*export default */new Vue/*.extend*/({
el: '#container',
template: `<div>
<div id="btn-box">
<button
v-for="(button, index) in buttons"
:key="index"
:class="button.isActive ? 'on' : 'off'"
:data-index="index"
#click="button.method">
<div :class="${"`"}btn btn-${"$"}{button.type}${"`"}">{{ button.label }}</div>
</button>
</div>
</div>`,
data() {
return {
component: "button[0].name",
color: "",
buttons: [
{
label: "A",
isActive: false,
type: "border-left",
name: "BorderLeftComonent",
method: this.toggleShowPopup,
},
{
label: "A",
isActive: false,
type: "text-balloon",
name: "TextBalloonComponent",
method: this.handleClick,
},
{
label: "A",
isActive: false,
type: "dashed",
name: "DashedComponent",
method: this.handleClick,
},
],
};
},
methods: {
toggleShowPopup(event) {
this.isOpen = !this.isOpen;
this.handleClick(event)
},
activeBtn(event) {
index = event.currentTarget.dataset.index
this.buttons[index].isActive = !this.buttons[index].isActive;
},
handleClick(event) {
index = event.currentTarget.dataset.index
button = this.buttons[index]
this.component = button.name;
this.activeBtn(event)
},
},
computed: {
currentComponent() {
return this.component;
},
cssVars() {
return {
'--border-left': this.color,
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="container"></div>

Related

Vue2: Binding v-model via computed property?

I have this component that shows a whole bunch of different components. Like so:
computed: {
...mapGetters('forms', ['formErrors']),
input: {
get() {
return this.value;
},
set(val) {
this.$emit('input', val);
},
},
component() {
const components = {
'ls-text-field': () =>
import('../../../../common/ls-text-field.vue'),
'simple-date-picker': () =>
import('../../../../common/simple-date-picker.vue'),
select: 'v-select',
combobox: 'v-combobox',
};
return components[this.setting.component];
},
attributes() {
const attrs = {
'ls-text-field': {
label: this.setting.name,
},
'simple-date-picker': {},
select: {
label: 'Select this foo',
items: this.setting.options,
},
combobox: {
'return-object': true,
items: this.productList,
loading: this.loading_product_list,
'item-value': 'sku',
'item-text': 'sku',
label: this.setting.name,
name: this.setting.key,
},
};
return {
...attrs[this.setting.component],
'error-messages': this.formErrors(this.setting.key),
};
},
},
and the Template looks something like this:
<template>
<v-col md="4" cols="12">
<component
:is="component"
v-bind="attributes"
v-model="input"
:search-input.sync="searchSku"
/>
But you'll notice I had to do v-model in the template and not in the computed property. I suppose there is NO way to do this:
attributes() {
const attrs = {
'ls-text-field': {
label: this.setting.name,
},
'simple-date-picker': {},
select: {
label: 'Select this foo',
items: this.setting.options,
},
combobox: {
'return-object': true,
items: this.productList,
loading: this.loading_product_list,
'item-value': 'sku',
'item-text': 'sku',
label: this.setting.name,
name: this.setting.key,
'v-model': this.item.info.someKey // This doesn't seem possible
},
};

Vue 3 how to rerender properly when prop value is updated

I have a Vue component to display a list of data. It should receive the data and sortBy as props and render correctly. However, I have an extra button in the component that I want to update sortBy and re-render the new list but I don't know how to assign new data to computed property sortedData. Thanks a lot if I can have some advices.
<template>
<div>
<template v-for="(item, index) in sortedData" :key="index">
{{ item.name }}
</template>
<button #click.prevent="sortWith('color')">sort Color</button>
</div>
</template>
<script>
export default {
props: {
data: {
type: Array,
default: []
},
sortBy: {
type: String,
default: 'name'
},
},
methods: {
sort(array, sortBy) {
return array.sort(function (a, b) {
return b[sortBy] - a[sortBy]
})
},
sortWith(sortBy) {
// Need to sort the list and re-render new order
}
}
computed: {
sortedData() {
return this.sort(this.data, this.sortBy)
}
}
}
</script>
Try like following snippet:
const app = Vue.createApp({
data() {
return {
items: [{name: 'aaa', color: 'red'}, {name: 'ccc', color: 'purple'}, {name: 'fff', color: 'yellow'}, {name: 'bbb', color: 'blue'}, {name: 'eee', color: 'green'}],
sortingBy: 'name',
sortingOrder: false
}
},
methods: {
setSorting(item) {
this.sortingBy = item
this.sortingOrder = !this.sortingOrder
}
}
})
app.component('child', {
template: `
<div>
<template v-for="(item, index) in sortedData" :key="index">
<div>{{ item.name }} - {{ item.color }}</div>
</template>
<div class="sort">
<p>Sort By (child) :</p>
<div v-for="(title, i) in Object.keys(data[0])" :key="i" class="fields">
{{ title }}
</div>
</div>
</div>
`,
props: {
data: {
type: Array,
default: []
},
sortBy: {
type: String,
default: 'name'
},
sortOr: {
type: String,
default: false
},
},
data() {
return {
sortField: this.sortBy,
sortOrder: this.sortOr
}
},
methods: {
sort(array, sortBy) {
return array.sort((a, b) => {
let x = a[sortBy].toLowerCase()
let y = b[sortBy].toLowerCase()
if (this.sortOrder) {
return x > y ? -1 : 1;
} else {
return x > y ? 1 : -1;
}
return 0
})
},
sorting(item) {
this.sortField = item
this.sortOrder = !this.sortOrder
}
},
computed: {
sortedData() {
return this.sort(this.data, this.sortField)
}
},
watch: {
sortBy: function (val) {
this.sortField = val
},
sortOr: function (val) {
this.sortOrder = val
},
}
})
app.mount('#demo')
.sort {
display: flex;
align-items: center;
}
.fields {
margin-left: 1em;
}
<script src="https://unpkg.com/vue#3.2.29/dist/vue.global.prod.js"></script>
<div id="demo">
<div class="sort">
<p>Sort By (parent) :</p>
<div v-for="(title, i) in Object.keys(items[0])" :key="i" class="fields">
{{ title }}
</div>
</div>
<child :data="items" :sort-by="sortingBy" :sort-or="sortingOrder"></child>
</div>

Vue, re-ordering list when selecting rendered checkbox behaves funky

I have got an issue I can't get my head around, I have tried to reproduce the error below, however the below code works just fine!
So below is what is supposed to happen.
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<middleman :items="items" :selected="selected" #update="update"></middleman>
</div>
<script type="text/x-template" id="checkbox">
<label :for="$vnode.key">
{{ value }}
<input type="checkbox"
:value="value"
:id="$vnode.key"
#change="$emit('update', $event)"
:checked="isSelected(value)">
</label>
</script>
<script type="text/x-template" id="middleman">
<div>
<checkbox v-for="(item, index) in filtered"
:selected="selected"
:value="item.value"
#update="$emit('update', $event)"
:key="`checkbox-${index}`"></checkbox>
</div>
</script>
<script>
Vue.component('checkbox', {
props: ['value', 'selected'],
template: '#checkbox',
methods: {
isSelected(value) {
return this.selected.indexOf(value) > -1;
}
}
})
Vue.component('middleman', {
props: ['items', 'selected'],
template: '#middleman',
computed: {
filtered() {
return [...this.items].sort((a, b) => this.selected.includes(b.value) - this.selected.includes(a.value))
}
}
})
new Vue({
el: '#app',
data: {
items: [
{value: 'BMW', count: 1},
{value: 'AUDI', count: 1},
{value: 'VAUXHALL', count: 1},
{value: 'FIAT', count: 1},
{value: 'HONDA', count: 1},
{value: 'LANDROVER', count: 1},
],
selected: ['AUDI', 'HONDA']
},
methods: {
update(event) {
if (event.target.checked) {
this.selected.push(event.target.value);
return;
}
const index = this.selected.indexOf(event.target.value);
this.selected.splice(index, 1);
}
}
});
</script>
<style>
label {
display: block;
}
</style>
The above is pretty much what I have got. There is a root component, a middleman, and then the checkbox...
However mine is doing this:
Here is the code, very similar but using single file components:
// Root.vue
<template>
<div class="app-search">
<div class="app-search__facets">
<facet v-for="(facet, key) in facets"
#update="updateFacet(...$event, key)"
:facet="facet"
:label="key"
:data="items[key]"
:key="key"/>
</div>
</div>
</template>
<script>
import facet from '../Facets/Facet';
export default {
components: {
facet
},
data: function () {
return {
facets: {
manufacturer: [{"control": "checkbox", "type": "value", "size": 20, "selected": []}]
},
items: {
manufacturer: [
{value: 'BMW', count: 1},
{value: 'AUDI', count: 1},
{value: 'VAUXHALL', count: 1},
{value: 'FIAT', count: 1},
{value: 'HONDA', count: 1},
{value: 'LANDROVER', count: 1}
]
}
}
},
methods: {
updateFacet(action, value, facet) {
switch (action) {
case 'add':
this.facets[facet].selected.push(value);
break;
case 'remove':
let index = this.facets[facet].selected.indexOf(value);
this.facets[facet].selected.splice(index, 1);
break;
}
},
}
}
</script>
// Facet.vue
<template>
<div class="app-search__facet">
<p class="app-search__facet--title">{{ label }} ({{ selected }})</p>
<component :is="facet.control"
v-for="(value, index) in filter"
:data="value"
:value="facet.selected"
:key="`facet-${label}-${index}`"
#value="update($event)" />
</div>
</template>
<script>
import '#/prototypes/toPascalCase'
export default {
props: {
data: {
required: true,
type: Array
},
facet: {
required: true,
type: Object
},
label: {
required: true,
type: String
}
}
computed: {
selected() {
return this.facet.selected.length
},
filter() {
const selected = this.facet.selected;
return [...this.data].sort((a, b) => selected.includes(b.value) - selected.includes(a.value));
}
},
methods: {
component(control) {
return () => import(`./Controls/${control.toPascalCase()}`)
},
update(event) {
switch(this.facet.control) {
case 'checkbox':
const value = event.target.value;
if (event.target.checked && this.facet.selected.indexOf(value) === -1) {
this.$emit('update', [ 'add', value ])
break;
}
this.$emit('update', [ 'remove', value ])
break;
default:
console.log('Can\'t handle this facet');
break;
}
},
},
}
</script>
// Checkbox.vue
<template>
<label :for="$vnode.key" class="app-search__checkbox">
{{ data.value }} ({{ data.count }})
<input type="checkbox"
:id="$vnode.key"
:value="data.value"
#change="$emit('value', $event)"
:checked="isSelected">
</label>
</template>
<script>
export default {
props: {
data: {
required: true,
type: Object
},
value: {
required: true
}
},
computed: {
isSelected() {
return this.value.indexOf(this.data.value) > -1;
}
}
}
</script>
Anyone got any idea what is going on here?
The problem is caused by the combination of using v-for index to create a :key and sorting items after each select/deselect action. Do not use index in key - use something unique for each item, for example item.value
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.js"></script>
<div id="app">
<middleman :items="items" :selected="selected" #update="update"></middleman>
</div>
<script type="text/x-template" id="checkbox">
<label :for="$vnode.key">
{{ value }}
<input type="checkbox"
:value="value"
:id="$vnode.key"
#change="$emit('update', $event)"
:checked="isSelected(value)">
</label>
</script>
<script type="text/x-template" id="middleman">
<div>
<checkbox v-for="(item, index) in filtered"
:selected="selected"
:value="item.value"
#update="$emit('update', $event)"
:key="item.value"></checkbox>
</div>
</script>
<script>
Vue.component('checkbox', {
props: ['value', 'selected'],
template: '#checkbox',
methods: {
isSelected(value) {
return this.selected.indexOf(value) > -1;
}
}
})
Vue.component('middleman', {
props: ['items', 'selected'],
template: '#middleman',
computed: {
filtered() {
return [...this.items].sort((a, b) => this.selected.includes(b.value) - this.selected.includes(a.value))
}
}
})
new Vue({
el: '#app',
data: {
items: [
{value: 'BMW', count: 1},
{value: 'AUDI', count: 1},
{value: 'VAUXHALL', count: 1},
{value: 'FIAT', count: 1},
{value: 'HONDA', count: 1},
{value: 'LANDROVER', count: 1},
],
selected: ['AUDI', 'HONDA']
},
methods: {
update(event) {
if (event.target.checked) {
this.selected.push(event.target.value);
return;
}
const index = this.selected.indexOf(event.target.value);
this.selected.splice(index, 1);
}
}
});
</script>
<style>
label {
display: block;
}
</style>

Tree-view of Editable Text Areas in Vue.js

I have this fiddle:
https://jsfiddle.net/pnqzspoe/12014/
I want to modify it a bit and want to display each node as a text area containing the corresponding text. Further, I want to give an option to 'reply' to it. This would mean insertion of a new text area into which we can enter text.
Here is the code:
<script type="text/x-template" id="item-template">
<li>
<div
:class="{bold: isFolder}"
#click="toggle"
#dblclick="changeType">
{{ model.name }}
<span v-if="isFolder">[{{ open ? '-' : '+' }}]</span>
</div>
<ul v-show="open" v-if="isFolder">
<item
class="item"
v-for="(model, index) in model.children"
:key="index"
:model="model">
</item>
<li class="add" #click="addChild">+</li>
</ul>
</li>
</script>
<p>(You can double click on an item to turn it into a folder.)</p>
var data = {
name: 'My Tree',
children: [
{ name: 'hello' },
{ name: 'wat' },
{
name: 'child folder',
children: [
{
name: 'child folder',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
},
{ name: 'hello' },
{ name: 'wat' },
{
name: 'child folder',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
}
]
}
]
}
// define the item component
Vue.component('item', {
template: '#item-template',
props: {
model: Object
},
data: function () {
return {
open: false
}
},
computed: {
isFolder: function () {
return this.model.children &&
this.model.children.length
}
},
methods: {
toggle: function () {
if (this.isFolder) {
this.open = !this.open
}
},
changeType: function () {
if (!this.isFolder) {
Vue.set(this.model, 'children', [])
this.addChild()
this.open = true
}
},
addChild: function () {
this.model.children.push({
name: 'new stuff'
})
}
}
})
// boot up the demo
var demo = new Vue({
el: '#demo',
data: {
treeData: data
}
})
What would be the template for this use-case?
If I don't understand your question wrongly...
Replace
{{model.name}}
with
<textarea v-model="model.name"></textarea>
should work?

Vue js dynamic data component

I'm passing some dynamic data from a parent component to a child component using props .. So I would like to know how I can add myColor prop to total value and show it an render the result in a final value.
I've already update the post with the parent component (shapes) and the child component (colors)
I'm using Vue 2 and webpack.
//parent component
<v-layout row wrap primary-title v-for="shape in shapes" :key="shape.id">
<v-layout column>
<v-flex >
<v-subheader>{{shape.name}} {{shape.price}}€ {{selectedShape.price}}</v-subheader>
</v-flex>
</v-layout>
</v-layout>
<my-colors :myShape="selectedShape.price"></my-colors>
<script>
import Colors from './Colors.vue';
export default {
components: {
Colors
},
data() {
return {
selectedShape: {},
shapes: [{
id: 1,
name: "Square",
price: 4,
href: "../../static/square.jpg"
}, {
id: 2,
name: "Circle",
price: 6,
href: "../../static/circle.jpg"
}]
}
},
computed: {
totalShape: function() {
var totalShape = 0;
for (var i = 0; i < this.shapes.length; i++) {
if (this.shapes[i].selected) {
totalShape += this.shapes[i].price;
}
}
return totalShape;
}
},
methods: {
getSelectedShape() {
return this.selectedShape;
},
}
}
</script>
//child component
<v-layout>
<v-layout>
<v-flex >
<h3 >Total price:</h3>
</v-flex>
</v-layout>
<v-layout>
<v-flex
<v-subheader> {{total}} {{myShape}} €</v-subheader>
</v-flex>
</v-layout>
</v-layout>
<script>
export default {
props: ['myShape'],
data: () => ({
checked1: '',
showCart: false,
colors: [{
id: 1,
name: "white",
price: 2,
checked: '',
}, {
id: 2,
name: "black",
price: 2.0,
checked: '',
}, {
id: 3,
name: "Grey",
price: 2.25,
checked: '',
}, {
id: 4,
name: "Blue",
price: 1.6,
checked: '',
}, {
id: 5,
name: "Red",
price: 2.5,
checked: '',
}, {
id: 6,
name: "Yellow",
price: 2.75,
checked: '',
}],
}),
computed: {
total: function() {
var total = 0;
for (var i = 0; i < this.colors.length; i++) {
if (this.colors[i].checked) {
total += this.colors[i].price;
}
}
return total;
},
},
}
</script>
I do not understand your needs from this script, but be aware of one way data flow in Vue. So, you can send data from parent component to child component in which its will be accessible through props, but not from child component to parent. Use Vuex if you need two-way data flow between components.
var child = {
template: '#child',
props: ['fromParent']
}
Vue.component('parent', {
template: '#parent',
components: {
child: child
},
props: ['fromInstance']
})
new Vue({
el: '#app',
data: {
instanceData: {
text: 'Original value'
}
},
created () {
var self = this
setTimeout(_ => self.instanceData.text = 'Changed value', 3000)
}
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<parent :from-instance="this.instanceData"></parent>
</div>
<template id="parent">
<div>
<child :from-parent="this.fromInstance"></child>
</div>
</template>
<template id="child">
<p>{{this.fromParent.text}}</p>
</template>
Example with SELECT:
var child = {
template: '#child',
props: ['selected']
}
Vue.component('parent', {
template: '#parent',
components: {
child: child
},
props: ['options'],
data () {
return {
parentCar: 'none'
}
},
methods: {
update (e) {
this.parentCar = e.target.value
}
}
})
new Vue({
el: '#app',
data: {
items: {
audi: 'Audi',
bmw: 'BMW',
mercedes: 'Mercedes',
}
}
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<parent :options="this.items"></parent>
</div>
<template id="parent">
<div>
<select #change="update">
<option value="none" selected>Car</option>
<option v-for="(value, key) in options" :value="key">
{{ value }}
</option>
</select>
<child :selected="this.parentCar"></child>
</div>
</template>
<template id="child">
<p>{{ selected }}</p>
</template>
Example with checked / unchecked checkbox:
var child = {
template: '#child',
props: ['checked']
}
Vue.component('parent', {
template: '#parent',
components: {
child: child
},
data () {
return {
checkbox: false
}
}
})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<parent></parent>
</div>
<template id="parent">
<div>
<input type="checkbox" v-model="checkbox">
<child :checked="this.checkbox"></child>
</div>
</template>
<template id="child">
<p>{{ checked }}</p>
</template>

Categories