I've an array of objects (items) that can be incremented with other items, I show them by looping into the array.
see:
and I would like to high-light an item when I click on it (e.g. : another border-top-color), but I'm failing at targeting one specific element and applying a style without applying the style to the whole array. Any idea?
Template, I'm using vuedraggable, don't mind it:
<template #item="{ element }">
<div
class="item"
:key="element"
#click="
messageItemTitle(element.title);
messageItemID(element.id);
"
>
<div class="item-title">
{{ element.title }}
</div>
<div class="item-subType">
{{ element.type }}
</div>
</div>
</template>
The script : Well, none of what I've coded previously worked, so here's the data only :
data() {
return {
dragItems: dragItemsList, //15 items that I can clone into dropItems
dropItems: [], //Array where I can add the items from dragItems
};
},
You can conditionally apply class:
const app = Vue.createApp({
data() {
return {
items: [{id:1, title: 'aaa', type: 'type1'}, {id:2, title: 'bbb', type: 'type2'}, {id:3, title: 'ccc', type: 'type3'}],
selected: null
};
},
methods: {
message(el) {
this.selected = el.id
}
}
})
app.mount('#demo')
.item {
border: 3px solid transparent;
border-top-color: black;
}
.selected {
border-top-color: green;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div v-for="(element, i) in items" :key="i">
<!--<template #item="{ element }">-->
<div
class="item"
:class="selected === element.id && 'selected'"
:key="element"
#click="message(element)"
>
<div class="item-title">
{{ element.title }}
</div>
<div class="item-subType">
{{ element.type }}
</div>
</div>
<!--</template>-->
</div>
</div>
Related
I have an icon in the v-for loop and I want the action to be performed specifically to the one icon but it is happening for all the icons that are repeating because of v-for.
<template>
<div>
<tr
v-for="(randomData, randomIndex) in obj['randomData']"
:key="randomIndex"
class="random-table"
>
<td data-label="Activity Alert">
<el-popover
v-model="remindMeVisibility"
placement="left-start"
width="500"
trigger="manual"
class="popover-form"
>
<RemindMe />
<i
slot="reference"
class="el-icon-message-solid reminder-icon"
#click="remindMeVisibility = true"
></i>
</el-popover>
</td>
</tr>
</div>
</template>
<script>
export default {
data() {
return {
remindMeVisibility: false,
}
},
}
</script>
Here is a working example on how to have a specific popover edit per element for a given list.
<template>
<div>
<pre>{{ cityNames }}</pre>
<section>
<div v-for="city in cityNames" :key="city.id">
<span>City {{ city.name }}</span>
<el-popover v-model="city.popoverOpen" placement="top" width="160" class="popover">
<el-button slot="reference">Update visibility</el-button>
<p>Change the visibility of the City?</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" #click="toggleCityVisibility({ id: city.id, visibility: false })">
Hide it
</el-button>
<el-button type="primary" size="mini" #click="toggleCityVisibility({ id: city.id, visibility: true })">
Make visible
</el-button>
</div>
</el-popover>
</div>
</section>
</div>
</template>
<script>
export default {
data() {
return {
cityNames: [
{
id: 1,
name: 'beijing',
remindMeVisibility: false,
popoverOpen: false,
},
{
id: 2,
name: 'shanghai',
remindMeVisibility: false,
popoverOpen: false,
},
{
id: 3,
name: 'guangzhou',
remindMeVisibility: false,
popoverOpen: false,
}
]
};
},
methods: {
toggleCityVisibility({ id, visibility }) {
console.log('id', id, 'visibility', visibility)
this.targetedCity = this.cityNames.find(city => city.id === id)
this.targetedCity.remindMeVisibility = visibility
}
},
}
</script>
<style scoped>
.popover {
margin-left: 1rem;
}
</style>
Here, we do have a list of cities, and we want to toggle their visibility (remindMeVisibility).
The id in the list was given for the :key uniqueness + find it later on when editing the visibility of it.
I didn't expected popoverOpen to be required here, but it is (a global popover state is not feasible from what I've tried).
This is how it looks
This question already has answers here:
A way to render multiple root elements on VueJS with v-for directive
(6 answers)
Closed 2 years ago.
hopefully someone here will be able to help me with this problem.
I have the following data:
[
{
title: 'Header',
children: [
{
title: 'Paragraph',
children: [],
},
],
},
{
title: 'Container',
children: [
{
title: 'Paragraph',
children: [],
},
],
},
]
I want to render this in a list of <div> like this:
<div class="sortable-item" data-depth="1" data-index="0">Header</div> <!-- Parent -->
<div class="sortable-item" data-depth="2" data-index="0">Paragraph</div> <!-- Child-->
<div class="sortable-item" data-depth="1" data-index="1">Container</div> <!-- Parent -->
<div class="sortable-item" data-depth="2" data-index="0">Paragraph</div> <!-- Child-->
I have built a component that would be recursive, this is what I have so far:
<template>
<template v-for="(item, index) in tree">
<div
class="sortable-item"
:data-depth="getDepth()"
:data-index="index"
:key="getKey(index)"
>
{{ item.title }}
</div>
<Multi-Level-Sortable
:tree="item.children"
:parent-depth="getDepth()"
:parent-index="index"
:key="getKey(index + 0.5)"
></Multi-Level-Sortable>
</template>
</template>
<script>
export default {
name: 'MultiLevelSortable',
props: {
tree: {
type: Array,
default() {
return [];
},
},
parentDepth: {
type: Number,
},
parentIndex: {
type: Number,
},
},
methods: {
getDepth() {
return typeof this.parentDepth !== 'undefined' ? this.parentDepth + 1 : 1;
},
getKey(index) {
return typeof this.parentIndex !== 'undefined' ? `${this.parentIndex}.${index}` : `${index}`;
},
},
};
</script>
As you can see not only I have a <template> as the root element I also have a v-for, two "no no" for Vue.js. How can I solve this to render the list of elements like I pointed out above?
Note: I have tried vue-fragment and I was able to achieve the structure I wanted, but then when I tried using Sortable.js it didn't work, as if it wouldn't recognise any of the .sortable-item elements.
Any help will be greatly appreciated! Thank you!
Thanks to #AlexMA I was able to solve my problem by using a functional component. Here is what it looks like:
import SortableItemContent from './SortableItemContent.vue';
export default {
functional: true,
props: {
tree: {
type: Array,
default() {
return [];
},
},
},
render(createElement, { props }) {
const flat = [];
function flatten(data, depth) {
const depthRef = typeof depth !== 'undefined' ? depth + 1 : 0;
data.forEach((item, index) => {
const itemCopy = item;
itemCopy.index = index;
itemCopy.depth = depthRef;
itemCopy.indentation = new Array(depthRef);
flat.push(itemCopy);
if (item.children.length) {
flatten(item.children, depthRef);
}
});
}
flatten(props.tree);
return flat.map((element) => createElement('div', {
attrs: {
'data-index': element.index,
'data-depth': element.depth,
class: 'sortable-item',
},
},
[
createElement(SortableItemContent, {
props: {
title: element.title,
indentation: element.indentation,
},
}),
]));
},
};
The SortableItemContent component looks like this:
<template>
<div class="item-content">
<div
v-for="(item, index) in indentation"
:key="index"
class="item-indentation"
></div>
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">{{ title }}</div>
</div>
</div>
</template>
<script>
export default {
name: 'SortableItemContent',
props: {
title: String,
indentation: Array,
},
};
</script>
Given the data I have posted on my question, it now renders the HTML elements like I wanted:
<div data-index="0" data-depth="0" class="sortable-item">
<div class="item-content">
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Header</div>
</div>
</div>
</div>
<div data-index="0" data-depth="1" class="sortable-item">
<div class="item-content">
<div class="item-indentation"></div>
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Paragraph</div>
</div>
</div>
</div>
<div data-index="1" data-depth="0" class="sortable-item">
<div class="item-content">
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Container</div>
</div>
</div>
</div>
<div data-index="0" data-depth="1" class="sortable-item">
<div class="item-content">
<div class="item-indentation"></div>
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Paragraph</div>
</div>
</div>
</div>
Thank you again #AlexMA for the tip on Functional Components.
To explain the question, using the code from documentation as below:
<template>
<div>
<b-form-tags v-model="value" no-outer-focus class="mb-2">
<template v-slot="{ tags, inputAttrs, inputHandlers, addTag, removeTag }">
<b-input-group aria-controls="my-custom-tags-list">
<input
v-bind="inputAttrs"
v-on="inputHandlers"
placeholder="New tag - Press enter to add"
class="form-control">
<b-input-group-append>
<b-button #click="addTag()" variant="primary">Add</b-button>
</b-input-group-append>
</b-input-group>
<ul
id="my-custom-tags-list"
class="list-unstyled d-inline-flex flex-wrap mb-0"
aria-live="polite"
aria-atomic="false"
aria-relevant="additions removals"
>
<!-- Always use the tag value as the :key, not the index! -->
<!-- Otherwise screen readers will not read the tag
additions and removals correctly -->
<b-card
v-for="tag in tags"
:key="tag"
:id="`my-custom-tags-tag_${tag.replace(/\s/g, '_')}_`"
tag="li"
class="mt-1 mr-1"
body-class="py-1 pr-2 text-nowrap"
>
<strong>{{ tag }}</strong>
<b-button
#click="removeTag(tag)"
variant="link"
size="sm"
:aria-controls="`my-custom-tags-tag_${tag.replace(/\s/g, '_')}_`"
>remove</b-button>
</b-card>
</ul>
</template>
</b-form-tags>
</div>
</template>
<script>
export default {
data() {
return {
value: ['apple', 'orange', 'banana', 'pear', 'peach']
}
}
}
</script>
The above code gives the output as follows:
But when I have the values as an array of objects instead of short strings how to render the properties of the objects as tag elements?
for example: If I have value: [{name: 'apple', color: 'red'}, {name:'mango', color: 'yellow'}] then how to get the same output as above?
I tried something something like <strong>{{ tag.name }}</strong> and it doesn't work and gives me only empty tags to remove as follows:
Any ideas on how to achieve what I wanted to do here?
What you're trying to do is not support yet according to this issue
You'll have to map your array of objects to a string array and utilize that.
Then once you're ready to "use" your tags, you could map them back based on the original objects.
Here's a rather simple example of what could be done.
new Vue({
el: "#app",
computed: {
mappedTags() {
/* This is case sensitive */
return this.options.filter(option => this.value.includes(option.name))
}
},
data: {
value: [],
options: [{
name: 'Mango',
color: 'Orange'
},
{
name: 'Orange',
color: 'Orange'
},
{
name: 'Lemon',
color: 'Yellow'
},
{
name: 'Apple',
color: 'Red'
},
{
name: 'Banana',
color: 'Yellow'
},
]
}
})
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.4.0/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.2.2/dist/bootstrap-vue.js"></script>
<style>
/* Only added for better visibility on the text */
.text-stroke {
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
}
</style>
<div id="app" class="p-3">
Valid (<b>Casesensitive</b>) tags - [Banana, Apple, Orange, Mango]
<b-form-tags v-model="value" no-outer-focus class="mb-2">
<template v-slot="{ tags, inputAttrs, inputHandlers, addTag, removeTag }">
<b-input-group aria-controls="my-custom-tags-list">
<b-input
v-bind="inputAttrs"
v-on="inputHandlers"
placeholder="New tag - Press enter to add"></b-input>
<b-input-group-append>
<b-button #click="addTag()" variant="primary">Add</b-button>
</b-input-group-append>
</b-input-group>
<ul class="list-unstyled d-inline-flex flex-wrap mb-0">
<b-card
v-for="tag in mappedTags"
:key="tag.name"
:id="`my-custom-tags-tag_${tag.name.replace(/\s/g, '_')}_`"
tag="li"
class="mt-1 mr-1"
body-class="py-1 pr-2 text-nowrap"
>
<strong :style="`color: ${tag.color}`" class="text-stroke">
{{ tag.name }}
</strong>
<b-button
#click="removeTag(tag.name)"
variant="link"
>
Remove
</b-button>
</b-card>
</ul>
</template>
</b-form-tags>
{{ mappedTags }}
</div>
Replace {{ tag.name }} with {{ JSON.parse(tag).name }}.
That should solve the problem.
You have a similar example where I needed the input of the user and its selection and gather does two inputs values inside an array of objects.
You need to add a v-model to your inputs this way you can pass it to a custom function
<b-form-input
v-model="werkTagNombre"
<b-form-select
v-model="werkTagExp"
...
"#click="addingWerkTag({werkTagNombre, werkTagExp})"
Complete template code
<b-form-tags v-model="freelance.tags.nombre">
<template v-slot="{ inputAttrs, inputHandlers }">
<b-input-group aria-controls="tags-list">
<b-form-input
v-model="werkTagNombre"
v-bind="inputAttrs"
v-on="inputHandlers"
placeholder="Seleccione"
class="werk-input werk-shadow-input mr-2"
>
</b-form-input>
<b-input-group-append>
<b-form-select
v-model="werkTagExp"
:options="experienciaOptions"
class="werk-input werk-shadow-input ml-2 mr-2"
>
</b-form-select>
</b-input-group-append>
<b-input-group-append>
<b-button class="shadow-none ml-2" style="border-radius:6px;"#click="addingWerkTag({werkTagNombre, werkTagExp})"> ADD</b-button>
</b-input-group-append>
</b-input-group>
<span style="font-size: 10px; color: #a5a5a5;">TAGS:</span>
<ul id="tags-list" class="list-unstyled d-inline-flex flex-wrap mb-0 mt-1">
<b-badge
v-for="(werkTag, index) in freelance.tags"
:key="index"
tag="li"
class="mx-1 my-1 werk-tags"
>
{{werkTag.nombre}} {{werkTag.experiencia}}
</b-badge>
</ul>
</template>
</b-form-tags>
Script
export default {
data() {
return {
freelance: {
tags: [
{
nombre:" Test name",
experiencia:1
}
],
}
...
werkTagNombre: '',
werkTagExp: '0',
experienciaOptions: [
{ value: '0', text: 'Selecciona'},
{ value: 1, text: 'menor a 2 años'},
{ value: 2, text: '3 a 5 años'},
{ value: 3, text: 'mayor a 5 años'},
]
...
methods:{
addingWerkTag(valor){
this.freelance.tags.push({nombre: valor.werkTagNombre, exp: valor.werkTagExp});
},
...
}
If you want you can also pass the addTag default function like this:
Inside the template slot add the functions like "addtag"
<template v-slot="{ inputAttrs, inputHandlers, addTag }">
...
So you can add it to your custom function like this:
#click="addingWerkTag({ werkTagNombre, werkTagExp, addTag })"
My JSON looks like this:
users: [
{ 'name': 'User 1'},
{ 'name': 'User 2'},
{ 'name': 'User 3'},
{ 'name': 'User 4'},
{ 'name': 'User 5'},
{ 'name': 'User 6'},
]
Now i am looping this and i am displaying in the same div, But i need to diaplay the data by these conditions:
Up to length 3 should display in one div and rest things should display in another div(Ex another div right to that div). Here the JSON will be dynamic, The length may be <3 or >3. My requirement looks like this:
JSFiddle link
Vue is convenient to accomplish such tasks.
You need another two computed properties based on users.
template:
<div>{{left}}</div>
<div>{{right}}</div>
computed: {
left: function(){
return this.users.slice(0, 3);
},
reight: function() {
return this.users.slice(3);
}
}
Another option: https://jsfiddle.net/kth61cLu/1/
<div id="app">
<h3>Users</h3>
<div class="users1">
<div v-for="(user, index) in users" v-if="index < 3">
<p>{{user.name}}</p>
</div>
</div>
<div v-if="users.length > 3" class="users2">
<div v-for="(user, index) in users" v-if="index > 3">
<p>{{user.name}}</p>
</div>
</div>
</div>
Create a computed property that splits your array into two with the first having three elements and the second having the rest.
Then loop over the splitUsers array to display.
new Vue({
el: '#app',
data: {
users: [{"name":"User 1"},{"name":"User 2"},{"name":"User 3"},{"name":"User 4"},{"name":"User 5"},{"name":"User 6"}]
},
computed: {
splitUsers () {
const split = [ this.users.slice(0, 3) ]
if (this.users.length > 3) {
split.push(this.users.slice(3))
}
return split
}
}
})
#app { display: flex; }
#app div { padding: 1rem; border: 1px solid black; }
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<div id="app">
<div v-for="chunk in splitUsers">
<p v-for="user in chunk">{{ user.name }}</p>
</div>
</div>
OK, here is what you can try.
<h3>Users</h3>
<div v-for="(user) in users.slice(0,3)">
<p>{{user.name}}</p>
</div>
<div v-for="(user) in users.slice(3)">
<p>{{user.name}}</p>
</div>
Hope it helps!
Trying to create a dynamic form with a list of each product from a db. The form has the following fields for each product: product title, product price, quantity and total price. My issue is in the fact that I'm not sure how to add a v-model field for each product quantity input, since the list is being pulled from a v-for of all of the products. Here is part of my ProductsListForm vue component template:
<div v-for="product in products" :key="product.id" class="flex form-group">
<div class="flex p-title">
<label :for="product.title">{{ product.title }}</label>
<small>{{ product.price }}$</small>
</div>
<div class="flex p-input">
<input class="input" type="number" :name="product.title" v-model="quantity">
</div>
<div class="flex p-total">
<span>Total: {{ product.price * quantity}}</span>
</div>
</div>
export default {
props: ['products'],
data() {
return {
quantity: 0,
}
},
methods: {}
}
So my question is how can I bind quantity to each individual product? Right now, it obviously changes whenever ANY of the input fields are updated...
Any help will be greatly appreciated!
Rather than using v-model, you could instead listen for the input events on each element.
new Vue({
el: '#app',
data: () => ({
cart: [],
products: [{
id: 1,
name: 'foo'
},
{
id: 2,
name: 'bar'
},
{
id: 3,
name: 'baz'
}
]
}),
methods: {
updateCart(event, product) {
const index = this.cart.findIndex(i => i.name === product.name)
const item = {
name: product.name,
quantity: event.target.value
}
if (index !== -1) {
this.cart.splice(index, 1, item)
} else {
this.cart.push(item)
}
}
}
})
ul,
li {
list-style: none;
}
#app {
display: flex;
flex-direction: row;
justify-content: space-around;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<h5>products</h5>
<ul>
<li v-for="product in products" :key="product.id">
<label>{{ product.name }}</label>
<input #input="updateCart($event, product)" min="0" type="number">
</li>
</ul>
</div>
<div>
<h5>cart</h5>
<ul v-if="cart.length">
<li v-for="item in cart" :key="item.id">
<p>{{ item.name }} {{ item.quantity }}</p>
</li>
</ul>
<p v-else>no items in cart</p>
</div>
</div>
For the quantity to be related to the product (entry of products) - you will have to pass it to the products or make it an array by itself.
<div v-for="product in products" :key="product.id" class="flex form-group">
<div class="flex p-title">
<label :for="product.title">{{ product.title }}</label>
<small>{{ product.price }}$</small>
</div>
<div class="flex p-input">
<input class="input" type="number" placeholder="1" :name="product.title" v-model="quantity[product.id]">
</div>
<div class="flex p-total">
<span>Total: {{ product.price * getProductQuantity(product) }}</span>
</div>
</div>
export default {
props: ['products'],
data() {
return {
quantity: [],
}
},
methods: {
getProductQuantity(product) {
return this.quantity[product.id] || 0;
}
}
}