I have the following code (vue v3.2.33)
<template v-for="route of routes" :key="route.name">
<li :set="open = false">
<div class="collapse"
#click="open = !open"
:class="[open ? 'collapse-is-open' : '']">
</div>
</li>
</template>
Basically each li has a collapse and opens with the class collapse-is-open.
I tried to use a variable with the :set attribute and modify that variable but it doesn't seem to work.
If that can't be working, what would be the other way of doing this ? An object based on the value I set in :key that keeps track of all the states?
:set is syntax for the v-bind directive. It binds a property/attribute on the element, but there is no such set property for <li>. I think you're trying to create an ad-hoc variable named open for each <li>, but that feature doesn't exist in Vue (perhaps you're thinking of petite-vue's v-scope).
One solution is to create a separate data structure that contains the open-state of each <li>, and use that to update the class binding:
export default {
data() {
return {
routes: [⋯],
open: {} // stores open-state of each route item
}
}
}
<template v-for="route of routes" :key="route.name">
<li>
<div class="collapse"
#click="open[route.name] = !open[route.name]"
:class="[open[route.name] ? 'collapse-is-open' : '']">
</div>
</li>
</template>
demo 1
Alternatively, you could add that property to each array item in routes:
export default {
data() {
return {
routes: [
{
name: 'Route 1',
open: false,
},
{
name: 'Route 2',
open: false,
},
{
name: 'Route 3',
open: false,
},
],
}
},
}
<template v-for="route of routes" :key="route.name">
<li>
<div class="collapse"
#click="route.open = !route.open"
:class="[route.open ? 'collapse-is-open' : '']">
</div>
</li>
</template>
demo 2
Related
I am currently doing a website builder, where user can drag and drop to add element.
The drag and drop works well, but what i want is, how can i disable/hide the drop placeholder in the target container ?
As show in the image, whenever I hover on a container, it will show a copy of my dragging element by default, which I don't want.
Here is my code :
<template>
<div style="display : flex;">
<div id="dragArea">
<draggable
class="dragArea list-group"
:list="list1"
:group="{ name: 'item', pull: 'clone', put: false }"
:clone="cloneItem"
#change="log"
>
<div class="list-group-item" v-for="element in list1" :key="element.id">{{ element.name }}</div>
</draggable>
</div>
<div id="dropArea">
<draggable class="dragArea list-group" :list="list2" group="item" #change="log">
<div class="list-group-item" v-for="element in list2" :key="element.id">{{ element.name }}</div>
</draggable>
</div>
</div>
</template>
Script :
<script>
import draggable from "vuedraggable";
let idGlobal = 8;
export default {
name: "custom-clone",
display: "Custom Clone",
order: 3,
components: {
draggable,
},
data() {
return {
hover : false,
list1: [
{ name: "cloned 1", id: 1 },
{ name: "cloned 2", id: 2 },
],
list2: [
]
};
},
methods: {
log: function(evt) {
window.console.log(evt);
},
cloneItem({ name, id }) {
return {
id: idGlobal++,
name: name
};
},
},
};
</script>
On each of your <draggable> components within your <template>, you can set the ghost-class prop to a CSS class that hides the drop placeholder (ie. "ghost", or "dragging element" as you called it) using display: none; or visibility: hidden;.
For example:
In your <template>:
<draggable ghost-class="hidden-ghost">
and in the <style> section of your Vue Single File Component, or in the corresponding stylesheet:
.hidden-ghost {
display: none;
}
Working Fiddle
The ghost-class prop internally sets the SortableJS ghostClass option (see all the options here). The ability to modify these SortableJS options as Vue.Draggable props is available as of Vue.Draggable v2.19.1.
Hi I'm trying to send data from one component to another but not sure how to approach it.
I've got one component that loops through an array of items and displays them. Then I have another component that contains a form/input and this should submit the data to the array in the other component.
I'm not sure on what I should be doing to send the date to the other component any help would be great.
Component to loop through items
<template>
<div class="container-flex">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Name</p>
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in entries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry />
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
export default {
name: 'entry-list',
components: {
addEntry
},
data: function() {
return {
entries: [
{
name: 'Paul'
},
{
name: 'Barry'
},
{
name: 'Craig'
},
{
name: 'Zoe'
}
]
}
}
}
</script>
Component for adding / sending data
<template>
<div
class="entry-add"
v-bind:class="{ 'entry-add--open': addEntryIsOpen }">
<input
type="text"
name="addEntry"
#keyup.enter="addEntries"
v-model="newEntries">
</input>
<button #click="addEntries">Add Entries</button>
<div
class="entry-add__btn"
v-on:click="openAddEntry">
<span>+</span>
</div>
</div>
</template>
<script>
export default {
name: 'add-entry',
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push(this.newEntries);
this.newEntries = '';
},
openAddEntry() {
this.addEntryIsOpen = !this.addEntryIsOpen;
}
}
}
</script>
Sync the property between the 2:
<add-entry :entries.sync="entries"/>
Add it as a prop to the add-entry component:
props: ['entries']
Then do a shallow merge of the 2 and emit it back to the parent:
this.$emit('entries:update', [].concat(this.entries, this.newEntries))
(This was a comment but became to big :D)
Is there a way to pass in the key of name? The entry gets added but doesn't display because im looping and outputting {{ entry.name }}
That's happening probably because when you pass "complex objects" through parameters, the embed objects/collections are being seen as observable objects, even if you sync the properties, when the component is mounted, only loads first level data, in your case, the objects inside the array, this is performance friendly but sometimes a bit annoying, you have two options, the first one is to declare a computed property which returns the property passed from the parent controller, or secondly (dirty and ugly but works) is to JSON.stringify the collection passed and then JSON.parse to convert it back to an object without the observable properties.
Hope this helps you in any way.
Cheers.
So with help from #Ohgodwhy I managed to get it working. I'm not sure if it's the right way but it does seem to work without errors. Please add a better solution if there is one and I'll mark that as the answer.
I follow what Ohmygod said but the this.$emit('entries:update', [].concat(this.entries, this.newEntries)) didn't work. Well I never even need to add it.
This is my add-entry.vue component
<template>
<div
class="add-entry"
v-bind:class="{ 'add-entry--open': addEntryIsOpen }">
<input
class="add-entry__input"
type="text"
name="addEntry"
placeholder="Add Entry"
#keyup.enter="addEntries"
v-model="newEntries"
/>
<button
class="add-entry__btn"
#click="addEntries">Add</button>
</div>
</template>
<script>
export default {
name: 'add-entry',
props: ['entries'],
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push({name:this.newEntries});
this.newEntries = '';
}
}
}
</script>
And my list-entries.vue component
<template>
<div class="container-flex">
<div class="wrapper">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Competition Entries</p>
</div>
<div class="entries__header__search">
<input
type="text"
name="Search"
class="input input--search"
placeholder="Search..."
v-model="search">
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in filteredEntries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry :entries.sync="entries"/>
</div>
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
import pickWinner from '#/components/pick-winner.vue'
export default {
name: 'entry-list',
components: {
addEntry,
pickWinner
},
data: function() {
return {
search: '',
entries: [
{
name: 'Geoff'
},
{
name: 'Stu'
},
{
name: 'Craig'
},
{
name: 'Mark'
},
{
name: 'Zoe'
}
]
}
},
computed: {
filteredEntries() {
if(this.search === '') return this.entries
return this.entries.filter(entry => {
return entry.name.toLowerCase().includes(this.search.toLowerCase())
})
}
}
}
</script>
progressbar component receives props from parent component name activeStep that its value is active index of progressbar. my challenge is while rendering list of progressbar i want to set active class to li s that less than value of active step property or equal to value
how can i do that?
<template>
<div>
<ul class="progressbar">
<li v-for="(progressbarCase,index) in progressbarCases" :key="progressbarCase.id">
{{progressbarCase.title}}
</li>
</ul>
</div>
</template>
<style lang="css">
#import "../assets/stylesheet/progressbar.css";
</style>
<script>
export default {
props: ['activeStep'],
data () {
return {
progressbarCases: [
{title: 'first'},
{title: 'second'},
{title: 'third'}
]
}
}
}
</script>
If you want add class conditional, you can use :class="". For example: :class="true ? 'active' : 'disable'". with this,if condition is true: class="active" else class="disable"
Quite simple, if you have more than one conditionnal class, you need to define the :class attribute as an object like this:
<li :class="{
'a-conditional-class': true,
'an-other-conditional-class-but-not-displayed': false,
}">
</li>
For your information, you can also couple conditional and unconditional classes !
And in one :class attribute, like this:
<li :class="[
'an-unconditional-class',
{
'a-conditional-class': true,
'an-other-conditional-class-but-not-displayed': false,
}
]">
</li>
Have a look on the official documentation for more information about Class an style binding: https://v2.vuejs.org/v2/guide/class-and-style.html#ad
My use case
I got an array from back-end API which contains input field names.
I render these names along with a input field.
This is my code.
<template>
<div class="itemGenerate">
<div>
<ul>
<li v-for="identifier in identifiers" :key="identifier">
<input type="text" value=""/>{{identifier.name}}
</li>
</ul>
<button type="button">Add</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
identifiers: [{ name: "Flavour" }, { name: "Size" }, { name: "Color" }]
};
}
};
My question is
these input fields are not constant. It'll change in each and every API call (some times it's only color sometimes color,size and another new thing).
I would use v-model if I know the number of input fields, but I cannot use here because it's not predefined.
How do I achieve this using vue.js?
try this out
<template>
<div class="itemGenerate">
<div>
<ul>
<li v-for="identifier in identifiers" :key="identifier">
<input type="text" v-model="item[identifier.name]"/>{{identifier.name}}
</li>
</ul>
<button type="button">Add</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
item:{},
identifiers: [{ name: "Flavour" }, { name: "Size" }, { name: "Color" }]
};
}
};
Im using Vue.js with Vue-router. I'm trying to create a list of components, each of which shows a subroute.
Example of What I am trying to do:
<ul>
<li>first
<ul><li>nested sub</li></ul>
</li>
<li>second
<ul><li>nested sub</li></ul>
</li>
<li>third
<ul><li>nested sub</li></ul>
</li>
</ul>
I am only able to get the nested subroute to appear in the first component. The rest of my components rendered in the v-for loop have no subroutes appearing.
Here is a fiddle of my code, showing the problem: https://jsfiddle.net/retrogradeMT/73zza1ah/
HTML:
<div id="page-content">
<router-view></router-view> <!-- renders campaign block list -->
</div>
<template id="campaignBlock" >
<ul>
<li v-for="campaign in campaigns">{{ campaign.name }}
<router-view> </router-view> <!-- should show subroute -->
</li>
</ul>
</template>
<template id="subroute">
<ul>
<li>Nested View</li>
</ul>
</template>
JS:
Vue.use(VueRouter)
Vue.component('app-page', {
template: '#campaignBlock',
data: function() {
return{
campaigns: []
}
},
ready: function () {
this.fetchCampaigns();
},
methods: {
fetchCampaigns: function(){
var campaigns = [
{
id: 1,
name: 'First List item'
},
{
id: 2,
name: 'Second List item'
},
{
id: 3,
name: 'Third List item'
},
];
this.$set('campaigns', campaigns);
}
}
})
Vue.component('subroute', {
template: '#subroute'
})
var App = Vue.extend({})
var router = new VueRouter()
router.map({
'/': {
component: Vue.component('app-page'),
subRoutes: {
'/': {
component: Vue.component('subroute')
},
}
}
})
router.start(App, '#page-content')
There can only be one single nested and identical router-view inside another. No way around it. For different routes, you can have multiple instances of router-view.
What you can do is something like this:
<li v-for="campaign in campaigns">{{ campaign.name }}
<subroute></subroute>
</li>