I made a component like treeview to show data like this:
[how my treeview shows][1]
<ul v-for="(build, index) in buildingList" :key="index">
<v-list-item>
<v-list-item-icon>
<v-icon v-show="build.apartments.length || build.guilds.length"
#click="showSub(index)">
{{ showAll ? ' mdi-chevron-up' : ' mdi-chevron-down' }}
</v-icon>
</v-list-item-icon>
</v-list-item>
<v-list-item-group v-show="currentIndex === index">
<li v-for="(apart) in build.apartments"
:key="apart.folderEid"
#load="getApartCode(apart.renewalCode)">
<v-list-item>
{{apart.title}}
</v-list-item>
</li>
<li v-for="(guild) in build.guilds"
:key="guild.folderEid">
<v-list-item>
{{guild.title}}
</v-list-item>
</li>
</v-list-item-group>
</ul>
when I want to open one of the list group items, I said get the index and open it. but now, I wanna make it show all the indexes when I open the page. the codes in data and methods:
data => currentIndex: -1,showAll: false,
in Methods:
methods: {
showSub(index) {
this.showAll = !this.showAll
this.currentIndex = this.currentIndex === index ? -1 : index
console.log(index, this.currentIndex)
},
async propertyTreeGet() {
this.loading = true
// this.showAll = true
// if ()
await this.$store.dispatch("axiosGet", {
url: `folder/api/properties/tree/${this.id}`
}).then(response => {
this.propertyList = response.data.data
this.loading = false
this.propertyTree.push(response.data.data)
this.propertyList.buildings.filter(build => {
this.buildingList.push(build)
return build.renewalCode = this.buildRenewal
})
this.getData()
})
},
}
I couldn't use the Vuetify treeview because in API we didn't have children but arrays of building, apartment, and guid inside the main array. Can anyone please give me a hint or a simpler example?
[1]: https://i.stack.imgur.com/0MmzE.png
Related
I'm using v-list-item-group and I want to show data in another component when the list item is selected. clear data when I unSelect item, and change data when I click on another list item
how can I possibly do it in vue?
the list item which I select:
Here I want to clear curr step data if the list.eid changed or when index changed
<v-list-item-group v-model="wfs">
<v-list-item v-for="(list,index) in workflowStepsList" :key="index"
#click="getWorkflowStep(list.eid)">
<v-list-item-action-text class="pe-4"> {{ index+1 }}</v-list-item-action-text>
<v-list-item-content v-if="!list.title">
{{ list.stepTitle }}
</v-list-item-content>
<v-list-item-content v-if="!list.stepTitle">
{{ list.title }}
</v-list-item-content>
<v-list-item-icon>
<v-icon small color="red">mdi-delete</v-icon>
</v-list-item-icon>
</v-list-item>
<v-list-item v-if="!workflowStepsList.length">
مرحله ای وجود ندارد
</v-list-item>
</v-list-item-group>
and the list I render data based on what I selected:
<v-card>
<v-card-title class="bg-success text-white d-flex justify-space-between">
مرحله فعلی
<add-curr :getSteps="getSteps"/>
</v-card-title>
<v-card-text>
<v-list>
<v-list-item-group class="v-list-item-group" v-model="stepId">
<v-list-item
v-if="!currStep.length"
class="text-muted"
>
یک مرحله انتخاب کنید
</v-list-item>
<v-list-item
v-for="(element) in currStep"
:key="element.eid"
v-show="element.eid !== null"
>
{{ element.title }}
</v-list-item>
</v-list-item-group>
</v-list>
</v-card-text>
</v-card>
the function:
async getWorkflowStep(weid) {
await this.$store.dispatch("axiosGet",
{url: `folder/api/workflow-steps/${weid}`, params: {workflowId: this.id}}).then(response => {
if (response.status === 'error') return
this.workflowStepsObj = response.data.data
const x = response.data.data
const curr = {
title: x.stepTitle,
eid: x.stepEid
}
this.currStep.push(curr)
})
},
I just added an another component and passing the model value as a prop in that component.
Live Demo :
Vue.component('another-component', {
props: ['val'],
template: '<h3>{{ val }}</h3>'
});
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
items: [
{ text: 'Inbox' },
{ text: 'Star' },
{ text: 'Send' },
{ text: 'Drafts' }
],
model: 1,
}),
});
<script src="https://unpkg.com/vue#2.x/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#2.6.12/dist/vuetify.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/vuetify#2.6.12/dist/vuetify.min.css"/>
<div id="app">
<v-list>
<v-list-item-group v-model="model">
<v-list-item v-for="(item, i) in items" :key="i">
{{ item.text }}
</v-list-item>
</v-list-item-group>
</v-list>
<another-component :val="items[model].text"></another-component>
</div>
You can either use store value and watch it from the component you want to track the change (it is possible to watch store properties if they change). If using vuex: https://vuex.vuejs.org/api/#watch
Or you can use emits and listen to the change on desired component, if there's depth issue for the emit you could pass emits on top level using (v-on="$listeners"): https://v2.vuejs.org/v2/guide/components-custom-events.html#Binding-Native-Events-to-Components
Since there's no statement about Vue or Vuetify version, inferring it is the 2nd version.
Sometimes when using Vuetify or UI libraries we may forget the features that Vue provides. Despite Vuetify having built-in features most probably they can be overridden by implementing yours on top of them.
I am using vuetify autocomplete component in my nuxt project. its not working as expected. problem is when i start typing it fetches data using api but it doesn't show me results in dropdown, but once i click outside input box and then again focus on search box, then it shows the dropdown. but i want to see the results on every key press.
here is the code so far.
<v-autocomplete v-model="pickupairport" name="pickupairport" :items="airports" :loading="isLoading" :search-input.sync="aptsearch" chips clearable hide-details hide-selected label="Search for a airport..." solo>
<template v-slot:no-data>
<v-list-item>
<v-list-item-title>
Search for your favorite
<strong>Destinations</strong>
</v-list-item-title>
</v-list-item>
</template>
<template v-slot:selection="{ attr, on, item, selected }">
<v-chip v-bind="attr" :input-value="selected" color="blue-grey" class="white--text" v-on="on">
<v-icon left>
mdi-bitcoin
</v-icon>
<span v-text="item.name"></span>
</v-chip>
</template>
<template v-slot:item="{ item }">
<v-list-item-avatar color="indigo" class="text-h5 font-weight-light white--text">
{{ item.name.charAt(0) }}
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="item.name"></v-list-item-title>
<v-list-item-subtitle>{{item.cityName}}, {{item.countryName}}</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<v-icon>fa-airports</v-icon>
</v-list-item-action>
</template>
</v-autocomplete>
this is script part
export default {
data() {
return {
aptsearch: null,
items: [],
model: null,
tab: null,
pickupairport: '',
airports: [],
isLoading: false,
uuid: this.$uuid.v4(),
}
},
watch: {
model(val) {
if (val != null) this.tab = 0
else this.tab = null
},
aptsearch(val) {
// Items have already been loaded
if (this.items.length > 0) return
this.isLoading = true
// Lazily load input items
fetch(`${process.env.EXPRESS_API_URL}/airports/find`, {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
aptsearchkey: this.aptsearch
})
})
.then(response => {
return response.json();
})
.then((data) => {
this.airports = []
data.forEach((item) => this.airports.push(item))
})
.catch(err => {
console.log(err)
})
.finally(() => (this.isLoading = false))
},
}
I tried to get data for my vuetify drop down list by using vue-infinite-loading. function is not calling automatically.
Below is my code
<v-menu offset-y offset-x bottom left max-height="500" min-width="400" max-width="450">
<template v-slot:activator="{ on }">
<el-badge :value="unReadMessage" :hidden="hidden" class="item">
<v-icon color="white" v-on="on">notifications</v-icon>
</el-badge>
</template>
<v-list v-for="(item, index) in notification_data" :key="index">
<v-list-tile>
<v-list-tile-content>
<v-list-tile-title>{{item.title}}</v-list-tile-title>
<v-list-tile-sub-title>{{item.description}}</v-list-tile-sub-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
<infinite-loading :identifier="infiniteId" #infinite="infiniteHandler" ref="infiniteLoading"></infinite-loading>
</menu>
Below is written in script. I already imported this "import InfiniteLoading from 'vue-infinite-loading';"
When i put the infinite loader out of the menu, its working but it shown the value out of the list
<script>
methods: {
infiniteHandler($state) {
this.loading = true;
this.axios.get(api, {
params: {
page: this.page,
},
})
.then(({data}) => {
if (data.next !== null) {
this.page += 1;
this.notification_data = this.notification_data.concat(data.results);
this.loading = false;
$state.loaded();
}
else {
this.loading = false;
this.notification_data = this.notification_data.concat(data.results);
$state.complete();
}
})
.catch(error => {
this.$notify({
type: 'error',
title: 'Error!',
message: error,
});
});
},
},
components: {
InfiniteLoading
}
</script>
On a button click based on value matching, I am trying populate an array with objects that i visualise in another components, here's how i do it:
This is the method that commits the mutation #click:
modifySelectedLimitCardStatus() {
const payload = [
this.editedLimitCard.id,
(this.editedLimitCard.limit_card_selected_status = true)
];
this.$store.commit("selectLimitCard", payload);
}
The state & mutation:
selectedLimitCard: [],
selectLimitCard: (state, payload) => {
state.limitCards.forEach(limitCard => {
if (
limitCard.id === payload[0] &&
limitCard.limit_card_selected_status !== payload[1]
) {
state.selectedLimitCard.push(limitCard);
console.log(state.selectedLimitCard);
}
});
}
And finally, the getter:
getSelectedLimitCard: state => {
return state.selectedLimitCard;
}
As you can see in the code, i log the state in the mutation and it's populated,
[{…}, __ob__: Observer]
however, in the component that is supposed to visualise the getter
<v-list :items="getSelectedLimitCard">
<v-list-tile>
<v-list-tile-content>№</v-list-tile-content>
<v-list-tile-content class="align-end">
{{ getSelectedLimitCard.limit_card_number }}
</v-list-tile-content>
</v-list-tile>
</v-list>
i get nothing.
Everything was working fine before i introduced the .push() way ot populating the array.
Can someone help me out & point out what i'm doing wrong?
Thanks in advance!
<v-list>
<v-list-tile v-for="card in getSelectedLimitCard" :key="card.limit_card_number">
<v-list-tile-content>№</v-list-tile-content>
<v-list-tile-content class="align-end">
{{ card.limit_card_number }}
</v-list-tile-content>
</v-list-tile>
</v-list>
I am trying to modify the sample code at https://vuetifyjs.com/en/components/autocompletes#example-scoped-slots to allow arbitrary content not matching any autocomplete items in between chips (so user can tag other users in a message similar to slack and facebook)
So for example, the user could type "Sandra" and then select "sandra adams", then type "foo" and then type another space and start typing "John" and the autcomplete would pop up again and allow the user to select "John Smith".
I've been through all the properties in the docs and there doesn't seem to be support for this built in.
I tried using custom filtering to ignore the irrelevant parts of the message when displaying autocomplete options, but the autocomplete seems to remove non-chip content when it loses focus and I can't see a property that allows me to prevent this behavior.
not sure if the autcomplete is the thing to be using or if I would be better off hacking combo box to meet this requirement, because this sample seems closer to what I'm tryng to do https://vuetifyjs.com/en/components/combobox#example-no-data, but then I believe I lose the ajax capabilities that come with automcomplete.
You can achieve this by combining the async search of the autocomplete with the combobox.
For example:
new Vue({
el: '#app',
data: () => ({
activator: null,
attach: null,
colors: ['green', 'purple', 'indigo', 'cyan', 'teal', 'orange'],
editing: null,
descriptionLimit: 60,
index: -1,
nonce: 1,
menu: false,
count: 0,
model: [],
x: 0,
search: null,
entries: [],
y: 0
}),
computed: {
fields () {
if (!this.model) return []
return Object.keys(this.model).map(key => {
return {
key,
value: this.model[key] || 'n/a'
}
})
},
items () {
return this.entries.map(entry => {
const Description = entry.Description.length > this.descriptionLimit
? entry.Description.slice(0, this.descriptionLimit) + '...'
: entry.Description
return Object.assign({}, entry, { Description })
})
}
},
watch: {
search (val, prev) {
// Lazily load input items
axios.get('https://api.publicapis.org/entries')
.then(res => {
console.log(res.data)
const { count, entries } = res.data
this.count = count
this.entries = entries
})
.catch(err => {
console.log(err)
})
.finally(() => (this.isLoading = false))
/*if (val.length === prev.length) return
this.model = val.map(v => {
if (typeof v === 'string') {
v = {
text: v,
color: this.colors[this.nonce - 1]
}
this.items.push(v)
this.nonce++
}
return v
})*/
},
model (val, prev) {
if (val.length === prev.length) return
this.model = val.map(v => {
if (typeof v === 'string') {
v = {
Description: v
}
this.items.push(v)
this.nonce++
}
return v
})
}
},
methods: {
edit (index, item) {
if (!this.editing) {
this.editing = item
this.index = index
} else {
this.editing = null
this.index = -1
}
},
filter (item, queryText, itemText) {
const hasValue = val => val != null ? val : ''
const text = hasValue(itemText)
const query = hasValue(queryText)
return text.toString()
.toLowerCase()
.indexOf(query.toString().toLowerCase()) > -1
}
}
})
<link href='https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons' rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js" integrity="sha256-mpnrJ5DpEZZkwkE1ZgkEQQJW/46CSEh/STrZKOB/qoM=" crossorigin="anonymous"></script>
<div id="app">
<v-app>
<v-content>
<v-container>
<v-combobox
v-model="model"
:filter="filter"
:hide-no-data="!search"
:items="items"
:search-input.sync="search"
hide-selected
label="Search for an option"
:allow-overflow="false"
multiple
small-chips
solo
hide-selected
return-object
item-text="Description"
item-value="API"
:menu-props="{ closeOnClick: false, closeOnContentClick: false, openOnClick: false, maxHeight: 200 }"
dark
>
<template slot="no-data">
<v-list-tile>
<span class="subheading">Create</span>
<v-chip
label
small
>
{{ search }}
</v-chip>
</v-list-tile>
</template>
<template
v-if="item === Object(item)"
slot="selection"
slot-scope="{ item, parent, selected }"
>
<v-chip
:selected="selected"
label
small
>
<span class="pr-2">
{{ item.Description }}
</span>
<v-icon
small
#click="parent.selectItem(item)"
>close</v-icon>
</v-chip>
</template>
<template
slot="item"
slot-scope="{ index, item, parent }"
>
<v-list-tile-content>
<v-text-field
v-if="editing === item.Description"
v-model="editing"
autofocus
flat
hide-details
solo
#keyup.enter="edit(index, item)"
></v-text-field>
<v-chip
v-else
dark
label
small
>
{{ item.Description }}
</v-chip>
</v-list-tile-content>
</template>
</v-combobox>
</v-container>
</v-content>
</v-app>
</div>
so I ended up building a renderless component that is compatible with vuetify as it goes through the default slot and finds any of the types of tags (textarea, input with type of text, or contenteditable) that tribute supports, and allows you to put arbitrary vue that will be used to build the tribute menu items via a scoped slot.
in future might try to wrap it as a small NPM package to anyone who wants a declarative way to leverage tribute.js for vue in a more flexible way than vue-tribute allows, but for now here's my proof of concept
InputWithMentions.vue
<script>
import Tribute from "tributejs"
// eslint-disable-next-line
import * as css from "tributejs/dist/tribute.css"
import Vue from "vue"
export default {
mounted() {
let menuItemSlot = this.$scopedSlots.default
let tribute = new Tribute({
menuItemTemplate: item =>
{
let menuItemComponent =
new Vue({
render: function (createElement) {
return createElement('div', menuItemSlot({ menuItem: item }))
}
})
menuItemComponent.$mount()
return menuItemComponent.$el.outerHTML
},
values: [
{key: 'Phil Heartman', value: 'pheartman'},
{key: 'Gordon Ramsey', value: 'gramsey'}
]})
tribute.attach(this.$slots.default[0].elm.querySelectorAll('textarea, input[type=text], [contenteditable]'))
},
render(createElement) {
return createElement('div', this.$slots.default)
}
}
</script>
User.vue
<InputWithMentions>
<v-textarea
box
label="Label"
auto-grow
value="The Woodman set to work at once, and so sharp was his axe that the tree was soon chopped nearly through.">
</v-textarea>
<template slot-scope="{ menuItem }">
<v-avatar size="20" color="grey lighten-4">
<img src="https://vuetifyjs.com/apple-touch-icon-180x180.png" alt="avatar">
</v-avatar>
{{ menuItem.string }}
</template>
</InputWithMentions>