vuedraggable custom style for each individual item - vuejs3

I need to have each vuedraggable item to have different styling in the wrapper tag (for example based on the element's index) like so:
<div class="wrapper">
<div class="grid_item" style="grid-row: 1">
I am Item 1
</div>
<div class="grid_item" style="grid-row: 2">
I am Item 2
</div>
<div class="grid_item" style="grid-row: 3">
I am Item 3
</div>
</div>
I know this is a very simple example that doesn't really need the index but suppose a more complex scenario where it is necessary to have access to the index in the draggable component (not its child template).
Suppose the current component looks like this:
<template>
<div class="wrapper">
<draggable
:list="items"
class="grid_item"
item-key="name">
<template #item="{ element }">
I am {{ element.name }}
</template>
</draggable>
</div>
</template>
<script>
import draggable from 'vuedraggable'
export default {
components: {
draggable,
},
data() {
return {
items: [
{
name: 'Item 1'
},
{
name: 'Item 2'
},
{
name: 'Item 3'
},
],
}
},
methods: {
rowForItem(index) {
return `grid-row: ${index + 1}`;
}
},
}
</script>
<style>
.wrapper {
display: grid;
}
.grid_item {
background-color: gray;
}
</style>
How can I make use of the rowForItem method here?

Related

css3 transtion not working with v-for in vue2

I use transtion in vue2. I added transition in css. Vue template show two box. One way use v-for and array, another way is use variable. btn2 is effective but btn1 not.
<style lang="sass">
.item
width: 120px
height: 120px
background-color: bisque
transition: margin-left 500ms
</style>
<template>
<div>
<div class="item" v-for="item in list" :key="item.index" :style="{marginLeft: item.index + 'px'}">{{ item.value }}</div>
<div class="item" :style="{marginLeft: left + 'px'}">123</div>
<button #click="addone">btn1</button>
<button #click="addtwo">btn2</button>
</div>
</template>
<script>
export default {
name: 'Heap',
data() {
return {
left: 100,
list: [
{
value: 12,
index: 10
}
]
}
},
methods: {
addone() {
this.list[0]['index']+=10
},
addtwo() {
this.left+=10
}
}
}
</script>
You are using the code :key="item.index" on your first div. Your code then updates that same index.
When a key's value changes, the component it is attached to re-renders. You are not seeing the animation occur because instead of dynamically incrementing the CSS, you are effectively just re-rendering the element with the new CSS.
The purpose of a key is to help Vue keep track of the identity of a given node in a list. It lets Vue know which nodes it can keep and patch up and which ones need to be rendered again.
You should use a static, non-changing value as a key where possible. In the following example I have added an id property to your object and used that as the key.
<style lang="sass">
.item
width: 120px
height: 120px
background-color: bisque
transition: margin-left 500ms
</style>
<template>
<div>
<div
v-for="item in list"
:key="item.id"
class="item"
:style="{marginLeft: item.index.toString() + 'px'}"
>
{{ item.value }}
</div>
<div class="item" :style="{marginLeft: left.toString() + 'px'}">123</div>
<button #click="addone">btn1</button>
<button #click="addtwo">btn2</button>
</div>
</template>
<script>
export default {
name: 'Example',
data() {
return {
left: 100,
list: [
{
id: '1',
value: 12,
index: 10,
},
],
};
},
methods: {
addone() {
this.list[0].index += 10;
},
addtwo() {
this.left += 10;
},
},
};
</script>

creating overlapping user icons

I'm trying to create a a component that overlaps over the user icons just as shown in the photo. I've seen this being used on google. If I have a list of user icons, how do I overlap them over each other?
I want something like this .
<template>
<div >
<ul>
<li v-for="user in userList" :key="user.key">
<user-icon-component :name="user.name" :image="user.picture"></user-icon-component>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "UserList",
props: {
userList: {
type: Object,
default: null,
},
},
};
</script>
<style>
</style>
The icon component is just an <img> tag with a user prop:
Vue.component('user-icon-component', {
props: ['user'],
template: `
<img :src="user.picture" width="32" height="32" />
`
})
Give the <li>s position: absolute and the <ul> position: relative to pull them out of the normal document flow. Set the left position on each <li> as a calculation from the index of the loop:
<ul class="icon-container">
<li v-for="(user, key, i) in userList" :key="user.key"
class="icon" :style="{ left: `${i * 20}px` }">
<user-icon-component :user="user"></user-icon-component>
</li>
</ul>
Here's a demo:
Vue.component('user-icon-component', {
props: ['user'],
template: `
<img :src="user.picture" width="32" height="32" />
`
})
/***** APP *****/
new Vue({
el: "#app",
data() {
return {
userList: {
'Bob': { name: 'Bob', key: 1, picture: 'https://www.flaticon.com/svg/static/icons/svg/3084/3084430.svg' },
'Mary': { name: 'Mary', key: 2, picture: 'https://www.flaticon.com/svg/static/icons/svg/3084/3084431.svg' },
'Paul': { name: 'Paul', key: 3, picture: 'https://www.flaticon.com/svg/static/icons/svg/3084/3084452.svg' },
}
}
},
});
.icon-container {
position: relative;
}
.icon {
position: absolute;
}
ul {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul class="icon-container">
<li v-for="(user, key, i) in userList" :key="user.key"
class="icon" :style="{ left: `${i * 20}px` }">
<user-icon-component :user="user"></user-icon-component>
</li>
</ul>
</div>
I liked the idea in your question and took it as a challenge for myself and here is the result:
vue overlapping avatars component
basically the approach I took was to use the component's props as style in style binding. there are some scoped style as well but I think they can be set in the style binding as well if needs be (probably the code could be cleaner).
user prop is an array of objects that contains this property: img: 'imageURL' and using a v-for on a div element with:
:style="{ backgroundImage: `url(${user.img})`}"
we can set the images.
as for the overlapping part, divs have position: relative and using the index of v-for, the style binding becomes like this:
:style="{backgroundImage: `url(${user.img})`, left: `-${i*15}px`}"
which shifts every element to the left by 15px except for the first one.
here is the image of the final result:
Thanks for your question, it was fun :)

How to display items in a CSS grid with Vue.js

I have a list of cars as below:
<script>
const sampleCars = [
{
id: 1,
name: 'Cressida',
model: 'XXC',
manufacturer: 'Toyota',
price: '$10,000',
inEditMode: false
},
{
id: 2,
name: 'Corolla',
model: 'ZD-2',
manufacturer: 'Toyota',
price: '$12,000',
inEditMode: false
},
{
id: 3,
name: 'Condor',
model: '27-9',
manufacturer: 'Mazda',
price: '$8,000',
inEditMode: false
}
]
export default {
data() {
return {
cars: sampleCars
}
}
}
</script>
<style>
.grid {
display: grid;
grid-template-columns: repeat(5, auto);
gap: 10px;
}
</style>
I want to display the items in a css grid with 5 columns.
If I use vue code like below:
<template>
<div >
<div class="grid">
<div>Name</div>
<div>Model</div>
<div>Manufacturer</div>
<div>Price</div>
<div>buttons</div>
</div>
<div v-for="car in cars" :key="car.id" class="grid">
<div>{{car.name}}</div> // "child div"
<div>{{car.model}}</div>
<div>{{car.manufacturer}}</div>
<div>{{car.price}}</div>
<div>buttons</div>
</div>
</div>
</template>
The problem with this code is that each item is displayed in its own grid. (And therefore not aligned as in image below). Using v-for all car properties become a child of the root div. So essentially I want all "child divs" to be a root of one CSS grid div. How can I achieve that?
With VueJS 2
Make the table header in one grid and table rows in another grid :
Vue.config.devtools = false;
Vue.config.productionTip = false;
const sampleCars = [{
id: 1,
name: 'Cressida',
model: 'XXC',
manufacturer: 'Toyota',
price: '$10,000',
inEditMode: false
},
{
id: 2,
name: 'Corolla',
model: 'ZD-2',
manufacturer: 'Toyota',
price: '$12,000',
inEditMode: false
},
{
id: 3,
name: 'Condor',
model: '27-9',
manufacturer: 'Mazda',
price: '$8,000',
inEditMode: false
}
]
new Vue({
el: '#app',
data() {
return {
cars: sampleCars
}
}
})
.grid {
display: grid;
grid-template-columns: repeat(5, minmax(92px,1fr));
gap: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app" class="container">
<div>
<div class="grid">
<div>Name</div>
<div>Model</div>
<div>Manufacturer</div>
<div>Price</div>
<div>buttons</div>
</div>
<div v-for="car in cars" :key="car.id">
<div class="grid">
<div>{{car.name}}</div>
<div>{{car.model}}</div>
<div>{{car.manufacturer}}</div>
<div>{{car.price}}</div>
<div>buttons</div>
</div>
</div>
</div>
</div>
The best solution I found was to use Vue-Fragment from https://github.com/Thunberg087/vue-fragment
The child elements in the v-for loop will end up being at the root level:
<template>
<div >
<div class="grid">
<div>Name</div>
<div>Model</div>
<div>Manufacturer</div>
<div>Price</div>
<div>buttons</div>
<fragment v-for="car in cars" :key="car.id" >
<div>{{car.name}}</div>
<div>{{car.model}}</div>
<div>{{car.manufacturer}}</div>
<div>{{car.price}}</div>
<div>buttons</div>
</fragment>
</div>
</div>
</template>
See also this SO question: Is there something like React.Fragment in Vue?

How to arrange elements in Vuejs component

I have a component in Vue like this:
Tags.vue
<template>
<div>
<v-chip
v-for="tag in tags"
:key="tag"
class="ma-2"
label
x-small
color="blue"
text-color="white"
>
{{ tag }}
</v-chip>
</div>
</template>
<script>
export default {
name: 'Tags',
props: {
tags: {
type: Array,
default: () => ['Empty'],
},
},
}
</script>
This is the result
How can I make them appear one below the other instead of side by side?
This should be a css questions. Add a class "container" for the wrapper div and put some css into it. You can use flex-box
<div class="container">
<v-chip
v-for="tag in tags"
:key="tag"
class="ma-2"
label
x-small
color="blue"
text-color="white"
>
{{ tag }}
</v-chip>
</div>
<style lang="scss" scoped>
.container{
display: flex,
flex-direction: 'column'
}
</style>
This is another solution without css:
I use tag https://vuetifyjs.com/en/components/chip-groups/
<template>
<div class="container">
<v-chip-group column active-class="primary--text">
<v-chip
v-for="tag in tags"
:key="tag"
class="ma-2"
outlined
x-small
color="blue"
text-color="blue"
>
{{ tag }}
</v-chip>
</v-chip-group>
</div>
</template>
<script>
export default {
name: 'Tags',
props: {
tags: {
type: Array,
default: () => ['Empty'],
},
},
}
</script>

How to hide wrapped components in React

I need to be able to hide components that gets wrapped because it goes over the max width.
<div style={{width:100}}>
<div style={{width:50}}>
component1
</div>
<div style={{width:50}}>
component2
</div>
<div style={{width:50}}>
component3
</div>
</div>
//But I actually use map to render children
<div style={{width:100}}>
{components.map((item, index) => {
return <div style={{width:50}}>component{index + 1}</div>)
}}
</div>
as shown in the code above, the parent div is of with 100. So the last component (component3) would go over the width of the parent by 50px and will be rendered in the second line. However, I want any component that leaves the first line to be not rendered at all. How do I make sure that only component1 and component2 shows and excludes component3?
You could add up the widths of all the components in a separate variable, and render null for all components remaining after the total width of those already rendered exceeds 100.
Example
class App extends React.Component {
state = {
components: [{ width: 50 }, { width: 50 }, { width: 50 }]
};
render() {
const { components } = this.state;
let totalWidth = 0;
return (
<div style={{ width: 100 }}>
{components.map((item, index) => {
totalWidth += item.width;
if (totalWidth > 100) {
return null;
}
return (
<div key={index} style={{ width: 50 }}>
component{index + 1}
</div>
);
})}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Resources