I'm building a list that users can choose items.
It would be a nested list with at least 3 layers.
As can only offer one layer of sub-option, I would like to build it as <ul> and <li>.
But I can't figure out how to change my code with two <ul> and <li>.
Hope someone could give me some ideas.
Here are what I have
<div id="applyApp" class="container">
<div class="pool">
<ul>
<h3 #click.prevent="isShow = !isShow">Category</h3>
<li v-for="items in filterData" :value="items.id">
{{items.id}} {{items.ame}}
</li>
</ul>
<ul class="selected-item">
<li v-for="items in secondLayer" :value="items.id">
{{items.id}} {{items.name}}
</li>
</ul>
</div>
Vue
new Vue({
el: "#applyApp",
data: {
firstLayer: [
{
name: "name1",
id: "0101",
},
{
name: "name2",
id: "010101",
},
{
name: "name3",
id: "010101001B",
},
],
secondLayer: [],
firstLayerValue: [],
secondLayerValue: [],
},
methods: {
moveHelper(value, arrFrom, arrTo) {
const index = arrFrom.findIndex(function (el) {
return el.id == value;
});
const item = arrFrom[index];
arrFrom.splice(index, 1);
arrTo.push(item);
},
addItems() {
const selected = this.firstLayerValue.slice(0);
for (const i = 0; i < selected.length; ++i) {
this.moveHelper(selected[i], this.firstLayer, this.secondLayer);
}
},
removeItems() {
const selected = this.secondLayerValue.slice(0);
for (const i = 0; i < selected.length; ++i) {
this.moveHelper(selected[i], this.secondLayer, this.firstLayer);
}
}
},
});
If you want nested items in your list, you might have to use a recursion component, which means adding child data to your items. And calling the component inside of itself until it exhausts the list of children, by taking the data props of the child in every call.
Vue.component("listRecursion", {
props: ['listData'],
name: "listRecursion",
template: `
<div>
<ul v-for="item in listData">
<li>{{item.name}}</li>
<list-recursion v-if="item.children.length" :list-data="item.children"/>
</ul>
</div>`
})
new Vue({
el: "#app",
data: {
todos: [{
name: "name1",
id: "0101",
children: [
{
name: "sub item 1",
id: "0d201",
children: []
},
{
name: "sub item 2",
id: "020g1",
children: []
},
{
name: "sub item 3",
id: "20201",
children: [
{
name: "subsub item 1",
id: "40201",
children: [
{
name: "subsub item 1",
id: "40201",
children: [
{
name: "subsubsub item 1",
id: "40201",
children: []
}]
}]
},
{
name: "subsub item 2",
id: "50201",
children: []
},
]
},
]
},
{
name: "name2",
id: "010101",
children: []
},
{
name: "name3",
id: "010101001B",
children: []
},
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Recursive list:</h2>
<list-recursion :list-data="todos"/>
</div>
As you can see, this saves you from manually adding new levels, just add to the data to the child nodes
I see #procoib which solves your first case and for the li rearrange, you can do the same approach similar to 'select' which is shown below.
new Vue({
el: "#app",
data() {
return {
first: [1, 2, 3, 4, 5],
second: []
}
},
methods: {
alter: function (index, src, desc) {
this[desc].push(this[src].splice(index, 1)[0])
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
<div id="app">
<h1>First</h1>
<ul id="firstList">
<li v-for="(_, i) in first" v-on:click="alter(i, 'first', 'second')">{{_}}</li>
</ul>
<h1>Second</h1>
<ul id="secondList">
<li v-for="(_, i) in second" v-on:click="alter(i, 'second', 'first')">{{_}}</li>
</ul>
</div>
If you click any number for a list, it is added to the other list.
Related
i'm showing up all category and subcategory with recursive component. i want to get all selected categor's id in parent component.
in my code when i select sub cat it does not return anything how can i fix this?
you can see live example here
https://codesandbox.io/s/eloquent-waterfall-3cpfe?file=/src/App.vue
parent component.
<template>
<div id="app">
<Child :cats.sync="selectedCategories" :categories="categories"/>
</div>
</template>
<script>
export default {
data: function() {
return {
categories: [
{ id: 1, name: "Category 1", children: [
{id: 11, name:"sub1", children: [
{id: 111, name: "sub 2", children: []}
]}
] },
{ id: 2, name: "Category 2", children: [] },
{ id: 3, name: "Category 3", children: [] }
],
selectedCategories: []
};
},
};
</script>
child component:
<template>
<div class="hello">
<label v-for="c in categories" :key="c.id">
<input v-model="temp" type="checkbox" :value="c">
{{ c.name }}
<template v-if="c.children.length > 0">
<Child :categories="c.children" />
</template>
</label>
</div>
</template>
<script>
export default {
name: "Child",
props: {
categories: Array,
cats: Array
},
computed: {
temp: {
get: function() {
return this.cats;
},
set: function(newValue) {
this.$emit("update:cats", newValue);
}
}
}
};
</script>
It does not work because you are binding subcategories with <HelloWorld v-model="temp" :categories="c.children" /> instead of <HelloWorld :cats.sync="temp" :categories="c.children" />
What is an appropriate way to achieve a nested "n" levels of routes based on currently selected category ?
I would like to properly access the routes, so my URL would look like this:
/categories
/categories/outfit/
/categories/outfit/jackets/
/categories/outfit/jackets/mountains/
/categories/outfit/jackets/mountains/you_get_the_idea
I have a list of categories like so:
const categories = [
{
name: 'outfit',
childs: [
{ name: 'jackets',
childs: [
{ name: 'mountains' }
]
},
{ name: 'pants', childs: ['many subcategories'] },
{ name: 'boots', childs: ['many subcategories'] }
]
},
{
name: 'knives',
childs: [
{ name: 'hunting' },
{ name: 'souvenirs' },
{ name: 'kitchen' },
{ name: 'skinning' },
{ name: 'throwing' }
]
}
]
I have a main page of Categories (TOP LEVEL):
<div class="col-6" v-for="category in categories" :key="category.id">
<div class="block q-pa-md">
<router-link :to="'/categories/' + category.slug">
<h4>{{ category.name }}</h4>
</router-link>
</div>
</div>
And there is a nested page for 1st level of childs:
<h1>{{ category.name }}</h1>
<q-img :src="category.image"/>
<p>{{ category.description }}</p>
<div class="row q-col-gutter-md">
<div class="col-md-4 col-xs-6" v-for="subcategory in category.childs" :key="subcategory.id">
<div class="block q-pa-md">
<router-link :to="'/categories/' + subcategory.id">
<h4>{{ subcategory.name }}</h4>
</router-link>
</div>
</div>
</div>
How would I do some repeating nested childs ?
Dynamic Route Matching feature of Vue router allows to define a dynamic parameter which captures more than one segment of the path by defining your route as /categories/:categoryId+ (router uses path-to-regex to match)
When defined this way, route as /categories/outfit/jackets/mountains will have a categoryId parameter with value outfit/jackets/mountains, which can be passed into the component, easily parsed and worked with...
See my example below:
Vue.config.devtools = false
Vue.config.productionTip = false
const categories = [{
name: 'outfit',
childs: [{
name: 'jackets',
childs: [{
name: 'mountains'
}]
},
{
name: 'pants',
childs: [{
name: 'short'
}, {
name: 'long'
}]
},
{
name: 'boots',
childs: [{
name: 'barefoot'
}, {
name: 'mountains'
}]
}
]
},
{
name: 'knives',
childs: [{
name: 'hunting'
},
{
name: 'souvenirs'
},
{
name: 'kitchen'
},
{
name: 'skinning'
},
{
name: 'throwing'
}
]
}
]
const cat = Vue.component('categories', {
data: function() {
return {
categories: categories // in real life, this data is shared for example using Vuex
}
},
template: `
<div>
<template v-for="cat in categories">
<router-link :to="'/categories/'+cat.name" :key="cat.name"> {{ cat.name }} </router-link>
</br>
</template>
</div>
`
})
const catView = Vue.component('category-view', {
data: function() {
return {
categories: categories // in real life, this data is shared for example using Vuex
}
},
props: ['categoryId'],
template: `
<div>
<hr>
<router-link :to="parentCategoryPath"> <- Back </router-link>
<div>Params: {{ categoryId }}</div>
<hr>
<template v-if="categoryDefinition && categoryDefinition.childs">
<template v-for="cat in categoryDefinition.childs">
<router-link :to="$route.path+ '/' +cat.name" :key="cat.name"> {{ cat.name }} </router-link>
</br>
</template>
</template>
</div>
`,
computed: {
categoryDefinition() {
let subCategory = this.categories.find(cat => cat.name === this.categoryId[0]);
for (i = 1; i < this.categoryId.length; i++) {
subCategory = subCategory.childs.find(cat => cat.name === this.categoryId[i])
}
return subCategory
},
parentCategoryPath() {
return '/categories/' + this.categoryId.slice(0, -1).join('/')
}
}
})
const router = new VueRouter({
base: '/js',
mode: 'history',
routes: [{
path: '/',
redirect: '/categories',
},
{
path: '/categories',
component: cat,
},
{
path: '/categories/:categoryId+',
component: catView,
props: route => ({
categoryId: route.params.categoryId.split('/')
})
}
]
})
const vm = new Vue({
el: '#app',
router,
data: function() {
return {}
}
})
hr {
border: none;
border-top: 3px double #333;
color: #333;
overflow: visible;
text-align: center;
height: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.5.1/vue-router.min.js" integrity="sha512-c5QVsHf336TmWncPySsNK2ncFiVsPEWOiJGDH/Le/5U6q1hU6F5B7ziAgRwCokxjmu4HZBkfWsQg/D/+X3hFww==" crossorigin="anonymous"></script>
<div id="app">
{{ $route.path }}
<router-view></router-view>
</div>
First, please check this pen I found, the concept is similar to my question, ReactJS - Baby Name Inspiration. I hope to make it via Vue.js but sorry I don't know React.
The question I want to ask if the user click the list item from Array 1, I named Array 1 as animals, the structure will show below. Then, it will pass the clicked item to Array 2, Array 2 as wished_pets_list. If for example {displayName: "Kitty", value: "cat"} clicked from animals list, animals & wished_pets_list also stored this object. When the same object in two arrays, the render of animals element will hide the object's output in HTML; it also renders to wished_pets_list as button. If click wished_pets_list's item button, it will remove the object data from wished_pets_list, and can access back on animals HTML list. And it can loop again.
The setting of data, default:
data: () => ({
animals: [
{displayName: "Kitty", value: "cat"},
{displayName: "Puppy", value: "dog"},
{displayName: "Chick", value: "bird"},
{displayName: "Fawn", value: "Deer"},
{displayName: "Joey", value: "Kangaroo"},
{displayName: "Piglet", value: "pig"},
{displayName: "Fry", value: "fish"},
{displayName: "Polliwog", value: "frog"}
],
wished_pets_list: [],
wished_pets_list_formatted: []
}),
Something I try on make it as HTML:
<div v-for="item in wished_pets_list">
<span #click="removeSelected(item.value)">{{item.displayName}}</span>
</div>
<div class="dropdown-list-container">
<div class="dropdown-list" v-for="(item, index) in animals">
<label :for="'givenID' + item.index" #click="pushSelect(item.value)">{{index}}{{item.displayName}}</label>
<input type="checkbox" v-model="wished_pets_list" :value="{'displayName': item.displayName, 'value': item.value}" :id="givenID' + item.index">
</div>
</div>
<!-- a hidden text field to submit the formatted as value only -->
<input type="text" v-model="wished_pets_list_formatted" name="anyName" v-show>
Two methods I think it should use:
methods: {
removeSelected(value){
this.wished_pets_list_formatted.push(value);
},
pushSelect(value){
this.wished_pets_list_formatted.splice(value);
}
},
Thanks, if you can, please make a similar codepen or jsfiddle.
Below is a implementation of the example codepen in Vue(didn't included the search part because I think it's irelevant in this case).
The template:
<div id="app">
<div data-reactroot="">
<main>
<div class="favourites">
<h4>Your Shortlist</h4>
<ul>
<li class="girl" v-for="(animal, index) in wished_pets_list" #click="removeFromList(index)">{{animal.displayName}}</li>
</ul>
<hr>
</div>
<ul>
<li v-for="(animal, index) in animals" :key="animal.value" class="boy" #click="addToList(index)">{{animal.displayName}}</li>
</ul>
</main>
</div>
</div>
The javascript part:
var vm = new Vue({
el: "#app",
data () {
return {
animals: [
{displayName: "Kitty", value: "cat"},
{displayName: "Puppy", value: "dog"},
{displayName: "Chick", value: "bird"},
{displayName: "Fawn", value: "Deer"},
{displayName: "Joey", value: "Kangaroo"},
{displayName: "Piglet", value: "pig"},
{displayName: "Fry", value: "fish"},
{displayName: "Polliwog", value: "frog"}
],
wished_pets_list: [],
wished_pets_list_formatted: []
}
},
methods: {
addToList(index) {
this.wished_pets_list.push(this.animals[index])
this.animals.splice(index, 1)
},
removeFromList(index) {
this.animals.push(this.wished_pets_list[index])
this.wished_pets_list.splice(index, 1)
}
}
});
For the CSS you can use the one from the codepen example.
Codepen fork
Base on #Allkin's answer, and my addition requirement, I tried to make such like Allkin's answer with an ordered list.
The template:
<div id="app">
<div>
<div class="favourites">
<h4>Your Shortlist</h4>
<ul>
<li class="girl" v-for="(animal, index) in wished_pets_list" #click="removeFromList(index, animal.value, animal.id)">{{animal.displayName}}</li>
</ul>
<hr>
</div>
<ul>
<li v-for="(animal, index) in animals" :key="animal.value" class="boy" #click="addToList(index, animal.value, animal.id)" v-show="!animal.checked">{{animal.displayName}}</li>
</ul>
<span>wished_pets_list_formatted:</span>
<div>{{wished_pets_list_formatted}}</div><br><br>
<span>animals:</span>
<div>{{animals}}</div>
</div>
</div>
js:
var vm = new Vue({
el: "#app",
data() {
return {
org_animal_list: [
{ displayName: "Kitty", value: "cat" },
{ displayName: "Puppy", value: "dog" },
{ displayName: "Chick", value: "bird" },
{ displayName: "Fawn", value: "Deer" },
{ displayName: "Joey", value: "Kangaroo" },
{ displayName: "Piglet", value: "pig" },
{ displayName: "Fry", value: "fish" },
{ displayName: "Polliwog", value: "frog" }
],
animals: null,
wished_pets_list: [],
wished_pets_list_formatted: []
};
},
methods: {
addToList(index, value, id) {
console.log("added: " + value);
this.wished_pets_list.push(this.animals[index]);
this.wished_pets_list_formatted.push(value);
this.animals[index].checked = !this.animals[index].checked;
},
removeFromList(index, value, id) {
var self = this;
this.wished_pets_list.splice(index, 1);
this.wished_pets_list_formatted.forEach(function(item, index) {
if (item == value) {
self.wished_pets_list_formatted.splice(index, 1);
}
});
for (var i = 0; i < this.animals.length; i++) {
if (self.animals[i].id == id) {
self.animals[i].checked = !self.animals[i].checked;
}
}
}
},
beforeMount: function() {
this.animals = this.org_animal_list;
for (var i = 0; i < this.animals.length; i++) {
this.$set(this.animals[i], "checked", false);
this.$set(this.animals[i], "id", i);
}
}
});
I added an original list first, then it will clone before Vue mount. This action allows the developer can use back the original data for other use.
For the full example, please check on codepen
So I have a button dropdown which is working as expected but I have a bug where I can't reuse the same component as they both dont work independently of each other but instead when one is clicked the other changes too.
Please find a JSFiddle below and my code.
Thanks
<div id="app">
<div v-on:click="menuVisible = !menuVisible" class="dropdownBtn">
<div v-bind:class="{ active : menuVisible }"class="dropdownBtn__title">
<span v-if="!btnTitle">exploring</span>
<span v-else>{{ btnTitle }}</span>
</div>
<div v-show="menuVisible" class="dropdownBtn__content">
<ul v-for="reason in reasons">
<li v-on:click="updateTitle(reason)">{{ reason.title }}</li>
</ul>
</div>
</div>
<div v-on:click="menuVisible = !menuVisible" class="dropdownBtn">
<div v-bind:class="{ active : menuVisible }"class="dropdownBtn__title">
<span v-if="!btnTitle">exploring</span>
<span v-else>{{ btnTitle }}</span>
</div>
<div v-show="menuVisible" class="dropdownBtn__content">
<ul v-for="reason in reasons">
<li v-on:click="updateTitle(reason)">{{ reason.title }}</li>
</ul>
</div>
</div>
</div>
new Vue({
el: '#app',
data() {
return {
menuVisible: false,
btnTitle: false,
reasons: [{
title: 'family fun',
value: 1
},
{
title: 'relaxing',
value: 2
},
{
title: 'dining',
value: 3
},
{
title: 'meetings & events',
value: 4
},
{
title: 'what\'s on',
value: 5
},
{
title: 'gold',
value: 6
}]
}
},
methods: {
updateTitle($event) {
this.btnTitle = $event.title
}
}
})
So, for one, you haven't actually made a reusable component for the dropdown. You need to define that using Vue.component. Then you can use the tag for the custom component in the root template.
Secondly, if you are binding to the same data, then that is what will be reflected in both templates. You'll still need to pass separate data to the two separate dropdown components.
Vue.component('dropdown', {
template: `
<div v-on:click="menuVisible = !menuVisible" class="dropdownBtn">
<div v-bind:class="{ active : menuVisible }"class="dropdownBtn__title">
<span v-if="!btnTitle">exploring</span>
<span v-else>{{ btnTitle }}</span>
</div>
<div v-show="menuVisible" class="dropdownBtn__content">
<ul v-for="reason in reasons">
<li v-on:click="updateTitle(reason)">{{ reason.title }}</li>
</ul>
</div>
</div>
`,
props: ['menuVisible', 'btnTitle', 'reasons'],
methods: {
updateTitle($event) {
this.btnTitle = $event.title
}
}
})
new Vue({
el: '#app',
data() {
return {
fooReasons: [
{ title: 'family fun', value: 1 },
{ title: 'relaxing', value: 2 },
{ title: 'dining', value: 3 },
{ title: 'meetings & events', value: 4 },
{ title: 'what\'s on', value: 5 },
{ title: 'gold', value: 6 }
],
barReasons: [
{ title: 'family bar', value: 1 },
{ title: 'bar relaxing', value: 2 },
{ title: 'bar dining', value: 3 },
{ title: 'meetings & bars', value: 4 },
{ title: 'bar\'s on', value: 5 },
{ title: 'gold bar', value: 6 }
]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<div id="app">
<dropdown :menu-visible="false" :btn-title="false" :reasons="fooReasons"></dropdown>
<dropdown :menu-visible="false" :btn-title="false" :reasons="barReasons"></dropdown>
</div>
Suppose I want to display a List of Questions. For each question, there is a list of answers, none of which are right or wrong. For each question, the user can choose an answer. I'm wondering how to create two-way binding on the selected answer.
The Vue:
new Vue(
{
el: "#app",
data:
{
questions: [{}]
}
}
Example Question Model:
{
id: 1,
name: "Which color is your favorite?",
selectedAnswerId: null,
selectedAnswerName: null,
answers:
[
{id: 1, name: red, photoUrl: ".../red", selected: false},
{id: 2, name: green, photoUrl: ".../green", selected: false},
{id: 3, name: blue, photoUrl: ".../blue", selected: false},
]
}
Components:
var myAnswer =
{
props: ["id", "name", "url", "selected"],
template:
`
<div class="answer" v-bind:class="{selected: selected}">
<img class="answer-photo" v-bind:src="url">
<div class="answer-name">{{name}}</div>
</div>
`
};
Vue.component("my-question",
{
props: ["id", "name", "answers"],
components:
{
"my-answer": myAnswer
},
template:
`
<div class ="question">
<div class="question-name">{{name}}</div>
<div class="question-answers">
<my-answer v-for="answer in answers" v-bind:id="answer.id" v-bind:name="answer.name" v-bind:url="answer.photoUrl" v-bind:selected="answer.selected"></my-answer>
</div>
</div>
`
});
When the user selects an answer to a question by clicking on the div, I want the Question model's selectedAnswerId/selectedAnswerName along with the answers selected property to be set accordingly. Therefore, what do I need to add to my components in order to accomplish this two-way binding? I believe it requires input elements and v-model, but I couldn't quite figure it out. Also, I am only one day into Vue.js and have no experience with related frameworks. So if I am doing anything blatantly wrong or against best practice, that would be good to know as well. Thanks in advance!
The answer will handle a click event and emit a (custom) selected-answer event. The question will have its own data item to store the selected answer ID; the answer component's selected prop will be based on that. The question will handle the selected-answer event by setting its selectedId.
var myAnswer = {
props: ["id", "name", "url", "selected"],
template: `
<div class="answer" v-bind:class="{selected: selected}"
#click="setSelection()"
>
<img class="answer-photo" :src="url">
<div class="answer-name">{{name}}</div>
</div>
`,
methods: {
setSelection() {
this.$emit('selected-answer', this.id);
}
}
};
Vue.component("my-question", {
props: ["id", "name", "answers"],
data() {
return {
selectedId: null
};
},
components: {
"my-answer": myAnswer
},
template: `
<div class ="question">
<div class="question-name">{{name}}</div>
<div class="question-answers">
<my-answer v-for="answer in answers"
:id="answer.id" :name="answer.name" :url="answer.photoUrl"
:selected="answer.id === selectedId" #selected-answer="selectAnswer"></my-answer>
</div>
</div>
`,
methods: {
selectAnswer(answerId) {
this.selectedId = answerId;
}
}
});
new Vue({
el: '#app',
data: {
questions: [{
id: 1,
name: "Which color is your favorite?",
answers: [{
id: 1,
name: 'red',
photoUrl: ".../red"
},
{
id: 2,
name: 'green',
photoUrl: ".../green"
},
{
id: 3,
name: 'blue',
photoUrl: ".../blue"
},
]
}]
}
});
.answer {
cursor: pointer;
}
.selected {
background-color: #f0f0f0;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<div id="app">
<my-question v-for="q in questions" :name="q.name" :answers="q.answers"></my-question>
</div>