I am using Bootstrap Vue to build a reusable Drowndown component. I would like to dynamically render different elements: item, title, and divider. How can I do this?
So what I want to achieve is the Dropdown component like this:
<template>
<b-dropdown>
<b-dropdown-{{option.element}}
v-bind:key="index"
v-for="option in data"
:value="option.id"
>{{ option.value }}</b-dropdown-{{option.element}}
>
</b-dropdown>
</template>
<script>
export default {
name: 'Dropdown',
props: {
data: data
}
}
</script>
So that it would render like
<b-dropdown-title>I am a title</b-dropdown-title>
<b-dropdown-item>And I am an item</b-dropdown-item>
<b-dropdown-item>I am another item</b-dropdown-item>
<b-dropdown-divider></b-dropdown-divider>
Then from the parent component, I could pass data like:
<Dropdown id="modules-dropdown" v-data="data"></Dropdown>
import Dropdown from './Dropdown'
const dropdownData = [{id: 1, value: 'I am a title', element: 'title'}, {id: 2, value: 'And I am an item', element: 'item'}, {id: 3, value: 'I am another item', element: 'item'}, {id: 4, element: 'divider'}]
export default {
name: 'ParentComponent',
components: {
Dropdown
},
data () {
return {
data: dropdownData,
}
},
}
What you want is a Dynamic Component, which allows you to define which component should be rendered using the is prop.
new Vue({
el: '#app',
data() {
return {
options: [
{ value: "Hello", element: 'header' },
{ value: "Item 1", element: 'item' },
{ value: "Item 2", element: 'item' },
{ value: null, element: 'divider' },
{ value: "Item 3", element: 'item' },
{ value: "Item 4", element: 'item' }
]
}
}
})
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.17.3/dist/bootstrap-vue.js"></script>
<link href="https://unpkg.com/bootstrap#4.5.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.17.3/dist/bootstrap-vue.css" rel="stylesheet" />
<div id="app">
<b-dropdown text="Dropdown">
<component
:is="`b-dropdown-${option.element}`"
v-for="(option, index) in options"
:key="index"
>
{{ option.value }}
</component>
</b-dropdown>
</div>
Related
I have quasar tabs that are being rendered in v-for loop.
Problem is that when I provide array as prop from parent, then my tabs stop working to toggle whereas it works with local array.
<div v-for="test in tests">
<q-tabs
v-model="test.status"
align="justify"
>
<q-tab
v-for="(tab, tabIndex) in tabs"
:key="tabIndex"
:name="tab.value"
>
{{ tab.name }}
</q-tab>
</q-tabs>
</div>
<script>
export default {
props: ['tests']
data (){
return {
tabs: [
{
name: 'Required',
value: 'required'
},
{
name: 'Not Required',
value: 'not-required'
}
]
}
}
Tests Data
[
{
id: "test 1"
name: 'TEST 1'
status: 'required'
},
{
id: "test 2"
name: 'TEST 2'
status: 'not-required'
}
]
Expected result
Toggle of tabs must work when component is provided with props data to iterate.
I want to use breadcrumbs in my vuetify project but I'm stuck somewhere. The point I'm stuck with is that when I click on bredcrumbs, the ones that come after that are deleted from the breadcrumbs list. For this, I tried the code below, but I could only delete the next element from it. Also, I couldn't delete the dividers. How do you think I can overcome this situation? thanks.
template:
<v-breadcrumbs :items="items" divider=">">
<template v-slot:item="{ item }">
<v-breadcrumbs-item :href="item.href" #click="showSelectedRow2(item)">
{{ item.text }}
</v-breadcrumbs-item>
</template>
</v-breadcrumbs>
Data:
items: [
{
text: '',
disabled: false,
id: '',
},
],
Script:
showSelectedRow2(item) {
for (var i = 0; i < this.items.length; i++) {
if (this.items[i].id == item.id) {
const index = this.items.indexOf(this.items[i]);
this.items.splice(index, 1);
}
this.items.push({
text: item.name,
disabled: false,
id: item.id,
});
}
},
eg Technology>Computer>laptop>Apple when I click on technology, only technology should remain in the list. I don't use route or link, I fill the list with data I get from an api.
You can try to change your method like in the following snippet:
(you can disable items or you can remove them)
new Vue({
el: '#app',
vuetify: new Vuetify(),
data() {
return {
items: [{text: 'Technology', disabled: false, id: 0,}, {text: 'Computer', disabled: false, id: 1,}, {text: 'Laptop', disabled: false, id: 2,}, {text: 'Apple', disabled: false, id: 3,},],
}
},
methods: {
itemIdx(itm) {
return this.items.findIndex(i => i.id === itm.id)
},
showSelectedRow2(item) {
//this.items = this.items.map(el => this.itemIdx(el) > this.itemIdx(item) ? {...el, disabled: true} : el)
this.items = this.items.filter(el => this.itemIdx(el) <= this.itemIdx(item))
}
}
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#6.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<div id="app">
<v-app>
<v-main>
<v-container>
<v-breadcrumbs :items="items" divider=">">
<template v-slot:item="{ item }">
<v-breadcrumbs-item :href="item.href" #click="showSelectedRow2(item)">
{{ item.text }}
</v-breadcrumbs-item>
</template>
</v-breadcrumbs>
</v-container>
</v-main>
</v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
i need to add an input field used to edit the title in the currently selected element component (selection was done by clicking). The problem is that there should be one input and work for each selected element. I couldn't find a similar task and solving on the Internet. Maybe someone will tell you how to do it?
ItemsList.vue component:
<template>
<input type="text" placeholder="Edit selected items"/>
<div class="items-col">
<ul class="items-list">
<Item v-for="item in items" :key="item" :title="item.title"/>
</ul>
</div>
</template>
<script>
import Item from '#/components/Item.vue'
export default {
data() {
return {
items: [
{ title: 'item 1' },
{ title: 'item 2' },
{ title: 'item 3' },
{ title: 'item 4' },
{ title: 'item 5' },
{ title: 'item 6' }
]
}
},
components: {
Item
}
}
</script>
Item.vue component:
<template>
<li class="item" #click="isActive = !isActive" :class="{ active: isActive }">{{ title }}</li>
</template>
<script>
export default {
name: 'ItemsList',
data() {
return {
isActive: false
}
},
props: {
title: String
}
}
</script>
<style>
.item.active {
color: red;
}
</style>
You might want to reconsider which component should be responsible of knowing which item is active at any point of time: hint: it should be the parent/consuming component. That is because you:
Have only a single input field, which means only one item can be edited at any point of time
You want to let the parent/consuming component to be the single source of truth of which item is actually active
Therefore, the first thing you should do is to ensure that isActive is a prop on the Item component, while the parent ItemList component keeps track of which is active at any point.
Then, it is simply a matter of:
Implementing a toggling logic for the isActive flag. The flag is updated when a native click event is fired from the Item component. For the toggling logic, we can simply toggle between a zero-based index of the click item, or -1, which we used to indicate that nothing is active.
Using v-bind:value and a computed property to reflect the value of the currently active item. We can simply retrieve it using this.items[this.activeIndex] on the parent component
Listening to the onInput event and then updating the correct item
See proof-of-concept below:
Vue.component('item-list', {
template: '#item-list-template',
data() {
return {
items: [{
title: 'item 1'
},
{
title: 'item 2'
},
{
title: 'item 3'
},
{
title: 'item 4'
},
{
title: 'item 5'
},
{
title: 'item 6'
}
],
activeIndex: -1,
}
},
methods: {
onItemClick(index) {
this.activeIndex = this.activeIndex === index ? -1 : index;
},
setActiveItemValue(event) {
const foundItem = this.items[this.activeIndex];
if (!foundItem) return;
return this.items[this.activeIndex].title = event.currentTarget.value;
}
},
computed: {
activeItemValue() {
return this.items[this.activeIndex]?.title ?? '';
}
}
});
Vue.component('item', {
template: '#item-template',
props: {
isActive: Boolean,
title: String
}
});
new Vue({
el: '#app'
});
li.active {
background-color: yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<item-list></item-list>
</div>
<script type="text/x-template" id="item-list-template">
<div>
<input type="text" placeholder="Edit selected items" :value="activeItemValue" #input="setActiveItemValue" />
<div class="items-col">
<ul class="items-list">
<item v-for="(item, i) in items" :key="i" :title="item.title" :isActive="activeIndex === i" #click.native="onItemClick(i)" />
</ul>
</div>
</div>
</script>
<script type="text/x-template" id="item-template">
<li class="item" :class="{ active: isActive }">{{ title }}</li>
</script>
If you want a solution with your current components (not the cleanest) , you can actually emit an event to the parent component when you activate an element that event should containe the index of the object in the items array
Then you can use the index to get and set the title variable , here is an example :
Item.vue
<template>
<li class="item" #click="activateItem" :class="{ active: isActive }">{{ title }}</li>
</template>
<script>
export default {
name: 'ItemsList',
data() {
return {
isActive: false
}
},
methods:{
activateItem() {
this.isActive = !this.isActive
this.$emit('activatedItem', this.isActive ? this.index : null)
}
},
props: {
title: String,
index: Number
}
}
</script>
<style>
.item.active {
color: red;
}
</style>
ItemList.vue
<template>
<div>
<input type="text" placeholder="Edit selected items" #input="inputChange" :value="inputValue"/>
<div class="items-col">
<ul class="items-list">
<Item v-for="(item, index) in items" :key="index" :title="item.title" :index="index" #activatedItem="itemSelected"/>
</ul>
</div>
</div>
</template>
<script>
import Item from '#/components/Item.vue'
export default {
data() {
return {
items: [
{ title: 'item 1' },
{ title: 'item 2' },
{ title: 'item 3' },
{ title: 'item 4' },
{ title: 'item 5' },
{ title: 'item 6' }
],
selectedIndex: null,
inputValue: ''
}
},
methods:{
itemSelected(index){
this.selectedIndex = index;
if(this.selectedIndex != null) {
this.inputValue = this.items[this.selectedIndex].title;
}
},
inputChange(event){
this.inputValue = event.target.value;
if(this.selectedIndex != null){
this.items[this.selectedIndex].title = this.inputValue
}
}
},
components: {
Item
}
}
</script>
You should also be aware that with the component Item you have given you can select more than one item !
I cant pass data to props in VueJS.
Here is my code:
const projects = [{ id: 1, name: 'First', img: "https://randomwordgenerator.com/img/picture-generator/53e0d7414855ac14f1dc8460962e33791c3ad6e04e5074417d2e72d2964cc6_640.jpg" },
{ id: 2, name: 'Second', img: "https://randomwordgenerator.com/img/picture-generator/paper-1100254_640.jpg" },
{ id: 3, name: 'Third', img: "https://randomwordgenerator.com/img/picture-generator/5fe2d4414352b10ff3d8992cc12c30771037dbf85254794075287cd69145_640.jpg" },
{ id: 4, name: 'Forth', img: "https://randomwordgenerator.com/img/picture-generator/5fe1d3414256b10ff3d8992cc12c30771037dbf8525478487c2f79d5924e_640.jpg" }
]
const Project = {
props: ['img', 'name'],
template: `
<div class="container projectbox">
<div class="textandimage"> Name: {{ projects.name }} </div>
</div>
`,
data() {
return {
projects
}
}
}
img:undefined
name:undefined
Please help.
Projects is a list (Array) of Objects. if you want to access an Object in the list, you need to reference its position. for example:
<div class="textandimage"> Name: {{ projects[0].name }} </div>
This will access the first Object in your list, and return the value of the key name
EDIT: for-loop
If you want to loop through the data to the template, do something like this:
<div v-for="(item, index) in projects" class="container projectbox" :key="index">
<div class="textandimage"> Name: {{ item.name }} </div>
</div>
Hope that makes more sense.
I am trying to make a custom list filter on input using computed property. In one file I create a widget, in another I use it. Here is the code from the widget creation file:
<template>
<input value="Гарантийный случай"
v-model="searchText"
:class="{'w-autocomplete__input_completed': completed}"
ref="input"
#click="areOptionsVisible = !areOptionsVisible"/>
<div v-if="areOptionsVisible"
:style="{maxHeight: maxHeight, overflow: 'auto', zIndex: zIndex}"
class="w-autocomplete__items">
<div v-for="option in options" class="w-autocomplete__item_first" >
{{ option.name }}
<div v-for="item in option.children" class="w-autocomplete__item"
:class="{'w-autocomplete__item_active': currentIndex === item}"
#mouseenter="setActive(item)"
#keyup.up="changeCurrent('up', item)"
#keyup.down="changeCurrent('down', item)"
#click="doChoose(item)">
{{ item.name }}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'dropdown',
props: {
placeholder: {
type: String,
required: false,
default: '',
},
options: {
type: Array,
default(){
return []
}
},
},
data: function () {
return {
counter: 0,
currentIndex: null,
completed: false,
chosenItem: null,
areOptionsVisible: false,
searchText: '',
data: [],
}
},
computed: {
options(){
return this.props.options.filter(elem => {
return elem.name.toLowerCase().includes(this.searchText.toLowerCase());
});
},
},
.......
}
</script>
This is how I pass the array to this list in another file:
<template>
........
<div class="complaint-form__line-item">
<div class="form-group">
<label>Гарантийный случай</label>
<dropdown :options="options" />
</div>
</div>
........
</template>
<script>
........
export default {
name: 'complaint-form',
components: {LockedImport, UploadFiles, CarNumberInput, Autocomplete, Dropdown},
props: {
......
}
},
data() {
const complaint = new Complaint();
return {
........
options: [
{name: 'Выход детали из строя в процессе эксплуатации', value: null,
children: [{name: 'Увеличение зазора, люфт (дробь/стуки)', value: 53},
{name: 'Обрыв детали', value: 54}]},
{name: 'Поломка при установке', value: null},
{name: 'Брак до установки', value: null,
children: [{name: 'Недокомплект', value: 55},
{name: 'Заводской брак (замятия, отсутствие резьбы, пробой пыльника и т.д.)',
value: 56}]},
],
}
},
Tell me please why computed doesn't work? Only I add computed and the list is not displayed at all when clicking on the field, but should. That is, it breaks down completely. I want to be filtered when entering text in input
Vue.js cannot have more than one element inside a <template> tag, so I would suggest that you enclose the whole code of the dropdown component within a <div> tag or something of the sort.
Also, and this is just a comment, I would suggest that you use the focus event for the input because with click it will still be showing even if you aren't focusing on the input.