I use vuetify and treeview. I want to list object fill with items and nested array in every of them.
export interface IExamMenuItem {
id?: number | string;
title: string;
link: string;
children: IExamMenuItem[];
}
<v-treeview :items="items" item-text="title" item-key="items.id" dense hoverable>
<template #prepend="{ item, open }">
<v-icon v-if="item.title === 'Амбулаторен лист'">
{{ 'home' }}
</v-icon>
<v-icon v-else-if="!item.name != 'Амбулаторен лист'">
{{ open ? 'mdi-folder-open' : 'mdi-folder' }}
</v-icon>
</template>
<template #label="{ item }">
<v-list-item
v-if="item.children"
:key="item.id"
style="background-color: beige"
:to="item.link"
link
>
{{ item.title }}
</v-list-item>
<v-list-item v-else :key="item.id" :to="item.link" link>
{{ item.title }}
</v-list-item>
</template>
</v-treeview>
When im trying to add or remove from 'items' i doesnt effect the tree, 'items' are update but not in the tree.
Here is the add method
private createReferralEventHandler(id: number | null, typeOfReferral: string | null) {
if (id != null && typeOfReferral != null) {
let item: IExamMenuItem = {} as IExamMenuItem;
const main = this.items.filter(x => x.id === typeOfReferral)[0];
if (main) {
item = {
id: id,
title: `${main.title} №${id}`,
link: `/Exam/${typeOfReferral}/Edit/${id}`
} as IExamMenuItem;
if (!main.children) {
main.children = [];
}
this.$set(this.items.filter(x => x.id === typeOfReferral)[0]['children'], main.children.length, item)
main.children.push(item);
this.titleOfReferral = item.title;
this.alertCreate = true;
}
}
Here is the the delete method
private deleteReferralEventHandler(id: number | null, typeOfReferral: string | null) {
if (id != null) {
const main = this.items.filter((x) => x.id === typeOfReferral)[0];
if (main) {
let remove = main.children.findIndex((i) => i.id === id);
this.titleOfReferral = main.children.filter((i) => i.id === id)[0].title;
main.children.splice(remove, 1);
this.alertDelete = true;
}
}
}
You have to re-assign items value to force vue to re-render the tree.
Should use something like this to remove an item:
this.items = this.items.filter((x) => x.id !== idToRemove)
And something like this to add an item:
this.items = [...this.items, newItem]
Related
I have two arrays in my Angular App, one is an array of object with categories and another array with items which has an object property in it which says to which category the item belong.
So i've made some custom pipes, one for which return all items if category "all" is selected and other two pipes for items array which return filtered items for each category and another pipe for search.
The items are rendered with category name when "all" is selected but when i'm searching for an item i would hide the category label if there are no items it it.
How could i archieve it?
Here is my *ngFor where i'm rendering the stuff:
<div
*ngFor="
let cat of category
| menuFiltered
: (selected === -1 ? -1 : category[selected].id)
"
class="products"
>
<h2>{{ category.desc }}</h2> // i need to hide this if the below plus array in search is empty
<hr />
<div
*ngFor="
let plu of plus
| pluFiltered: men.id
| pluSearch: searchModel
"
class="row mb-3 product"
>
...
</div>
</div>
EDIT:
The arrays looks like this:
menu = [{id: 123, codmen: 2, desc: "ANTIPASTI", estesa: ""}, {id: 127, codmen: 5, desc: "PRIMI", estesa: ""}, {id: 128, codmen: 6, desc: "SECONDI", estesa: ""}] // this is the "category"
plus = [{desc: "SFOGLIATINA AL CARTOCCIO", menu: 123}, {desc: "SFOGLIATINA AL CARTOCCIO", menu: 127}, {desc: "SFOGLIATINA AL CARTOCCIO", menu: 128}] // menu is the actualy id of menu array to which item belong
Once i get the items from my API i'm removing from menu array all objects without any item in it like this:
this.menu = this.menu.filter((menu) =>
this.plus.some((item) => item.menu === menu.id)
);
And here are my pipes:
menuFiltered pipe:
export class MenuFilteredPipe implements PipeTransform {
transform(list: any[], menu: number): any {
return list ? menu === -1 ? list : list.filter(item => item.id === menu) : [];
}
}
pluFiltered pipe:
export class PluFilteredPipe implements PipeTransform {
transform(list: any[], menu: number): any {
return list ? list.filter(item => item.menu === menu) : [];
}
}
And pluSearch pipe:
export class PluSearchPipe implements PipeTransform {
transform(list: any[], filterText: string): any {
return list ? list.filter(item => item.desc.search(new RegExp(filterText, 'i')) > -1) : [];
}
}
Try to use a ng-container with a *ngIf:
<div
*ngFor="let cat of category| menuFiltered: (selected === -1 ? -1 : category[selected].id)" class="products">
<ng-container *ngIf="plus | pluFiltered: men.id | pluSearch: searchModel as plusArray">
<h2 *ngIf="plusArray.length > 0">{{ category.desc }}</h2>
<div *ngFor="let plu of plusArray" class="row mb-3 product">
...
</div>
</ng-container>
</div>
I wanted to know if it is possible to move rows up & down?
I was using a checkbox feature, & the CRUD data table from the documetation.
I couldn't really find any examples in the documentation.
My v-data-table currently looks like this
<v-data-table
v-model="selected"
:headers="headers"
:items="rows"
:search="search"
disable-pagination
hide-default-footer
show-select
class="elevation-1" >
<template v-slot:item="props">
<tr>
<td>
<v-checkbox
v-model="props.selected"
:disabled="!props.selected && selected.length != 0"
:indeterminate="!props.selected && selected.length != 0"
></v-checkbox>
</td>
<td v-for="(prop, key) in props.item" :key="key" #click="onClickItem(key, props.item[key])">
{{props.item[key]}}</td>
<td>
<v-icon small class="mr-2" #click="editItem(props.item)">
mdi-pencil
</v-icon>
<v-icon small #click="deleteItem(props.item, getItemAtIndex(navItem))">
mdi-delete
</v-icon>
</td>
</tr>
</template>
<template> <!-- A dialog box for editing content-->
</template>
</v-data-table>
You can take a look at this example. The example has up and down arrow which you will click and it will update the items array. Note that you must you use Vue.$set to make the update to the items array reactive.
The example is done using vue-composition api and typescript
https://gist.github.com/JeremyWalters/457ea585bab678b3bafeb3ee16e96401
<template>
<v-data-table :headers="headers" :items="items">
<template v-slot:item.actionUp="{item}">
<v-btn color="success" icon #click="moveUp(item.id)">
<v-icon>mdi-arrow-up</v-icon>
</v-btn>
</template>
<template v-slot:item.actionDown="{item}">
<v-btn color="warning" icon #click="moveDown(item.id)">
<v-icon>mdi-arrow-down</v-icon>
</v-btn>
</template>
</v-data-table>
</template>
<script lang="ts">
import {
defineComponent,
SetupContext,
ref,
onMounted,
Ref
} from "#vue/composition-api";
export default defineComponent({
setup(props: any, context: SetupContext) {
const items: Ref<any[]> = ref([]);
const headers = [
{ text: "Test Value", value: "testValue" },
{ text: "", value: "actionUp" },
{ text: "", value: "actionDown" }
];
// Create data example
onMounted(() => {
for (let i = 0; i < 20; i++) {
items.value.push({ id: i, testValue: "testValue " + i });
}
});
// Move items up in the array
function moveUp(id: number) {
const index = items.value.findIndex(e => e.id == id);
if (index > 0) {
const el = items.value[index];
context.root.$set(items.value, index, items.value[index - 1]);
context.root.$set(items.value, index - 1, el);
}
}
// Move items down in the array
function moveDown(id: number) {
const index = items.value.findIndex(e => e.id == id);
debugger;
if (index !== -1 && index < items.value.length - 1) {
const el = items.value[index];
context.root.$set(items.value, index, items.value[index + 1]);
context.root.$set(items.value, index + 1, el);
}
}
return {
moveUp,
moveDown,
headers,
items
};
}
});
</script>
I have created the following component that wraps Vuetify VHover, VTooltip and VBtn to simplify my app.
<template>
<div>
<v-hover v-if="tooltip">
<v-tooltip
slot-scope="{ hover }"
bottom
>
<v-btn
slot="activator"
:theme="theme"
:align="align"
:justify="justify"
:disabled="disabled"
:depressed="type === 'depressed'"
:block="type === 'block'"
:flat="type === 'flat'"
:fab="type === 'fab'"
:icon="type === 'icon'"
:outline="type === 'outline'"
:raised="type === 'raised'"
:round="type === 'round'"
:color="hover ? colorHover : color"
:class="{ 'text-capitalize': label, 'text-lowercase': icon }"
:size="size"
#click="onClick()"
>
<span v-if="label">{{ label }}</span>
<v-icon v-else>{{ icon }}</v-icon>
</v-btn>
<span>{{ tooltip }}</span>
</v-tooltip>
</v-hover>
<v-hover v-else>
<v-btn
slot-scope="{ hover }"
:theme="theme"
:align="align"
:justify="justify"
:disabled="disabled"
:depressed="type === 'depressed'"
:block="type === 'block'"
:flat="type === 'flat'"
:fab="type === 'fab'"
:icon="type === 'icon'"
:outline="type === 'outline'"
:raised="type === 'raised'"
:round="type === 'round'"
:color="hover ? colorHover : color"
:class="{ 'text-capitalize': label, 'text-lowercase': icon }"
:size="size"
#click="onClick()"
>
<span v-if="label">{{ label }}</span>
<v-icon v-else>{{ icon }}</v-icon>
</v-btn>
</v-hover>
</div>
</template>
<script>
import VueTypes from 'vue-types'
export default {
name: 'v-btn-plus',
props: {
align: VueTypes.oneOf(['bottom', 'top']),
justify: VueTypes.oneOf(['left', 'right']),
color: VueTypes.string.def('primary'),
colorHover: VueTypes.string.def('secondary'),
disabled: VueTypes.bool.def(false),
icon: VueTypes.string,
label: VueTypes.string,
position: VueTypes.oneOf(['left', 'right']),
tooltip: VueTypes.string,
size: VueTypes.oneOf(['small', 'medium', 'large']).def('small'),
theme: VueTypes.oneOf(['light', 'dark']),
type: VueTypes.oneOf(['block', 'depressed', 'fab', 'flat', 'icon', 'outline', 'raised', 'round']).def('raised')
},
methods: {
onClick() {
this.$emit('click')
}
},
created: function() {
// Workaround as prop validation on multiple props is not possible
if (!this.icon && !this.label) {
console.error('[Vue warn]: Missing required prop, specify at least one of the following: "label" or "icon"')
}
}
}
</script>
<style scoped>
</style>
I want to test VHover and VTooltip and have defined the following spec file.
import { createLocalVue, mount } from '#vue/test-utils'
import Vuetify from 'vuetify'
import VBtnPlus from '#/components/common/VBtnPlus.vue'
describe('VStatsCard.vue', () => {
let localVue = null
beforeEach(() => {
localVue = createLocalVue()
localVue.use(Vuetify)
})
it('renders with default settings when only label is specified', async () => {
const label = 'Very cool'
const defaultColor = 'primary'
const defaultType = 'raised'
const defaultSize = 'small'
const wrapper = mount(VBtnPlus, {
localVue: localVue,
propsData: { label }
})
expect(wrapper.text()).toMatch(label)
expect(wrapper.html()).toContain(`${defaultType}="true"`)
expect(wrapper.html()).toContain(`size="${defaultSize}"`)
expect(wrapper.html()).toContain(`class="v-btn theme--light ${defaultColor} text-capitalize"`)
expect(wrapper.html()).not.toContain(`v-icon"`)
wrapper.find('button').trigger('mouseenter')
await wrapper.vm.$nextTick()
const btnHtml = wrapper.find('.v-btn').html()
expect(btnHtml).toContain('secondary')
expect(btnHtml).not.toContain('primary')
const tooltipId = btnHtml.match(/(data-v-.+?)(?:=)/)[1]
const tooltips = wrapper.findAll('.v-tooltip_content')
let tooltipHtml = null
for (let tooltip of tooltips) {
const html = tooltip.html()
console.log(html)
if (html.indexOf(tooltipId) > -1) {
tooltipHtml = html
break
}
}
expect(tooltipHtml).toContain('menuable_content_active')
})
})
The wrapper.find('button').trigger('mouseenter') does not work as expected. When I look at the html code after the nextTick it is the same as before trigger was called. It looks like I'm missing a part of the html. I was excpecting to see the following html.
<div data-v-d3e326b8="">
<span data-v-d3e326b8="" class="v-tooltip v-tooltip--bottom">
<span>
<button data-v-d3e326b8="" type="button" class="v-btn v-btn--depressed theme--light orange text-lowercase" size="small">
<div class="v-btn__content"><i data-v-d3e326b8="" aria-hidden="true" class="v-icon mdi mdi-account theme--light"></i></div>
</button>
</span>
</span>
</div>
All I'm getting is the <button> part.
Any suggestions how to get this to work?
Explicitly triggering mouseenter events doesn't normally have an effect, likely because browsers ignore simulated/non-trusted events. Instead, you could set v-hover's value to true to force the hover state:
VBtnPlus.vue
<template>
<div>
<v-hover :value="hovering">...</v-hover>
</div>
</template>
<script>
export default {
data() {
return {
hovering: false,
}
},
//...
}
</script>
VBtnPlus.spec.js
it('...', async () => {
wrapper.vm.hovering = true;
// test for hover state here
})
I just ran into this myself using Nuxt, Vuetify, and Jest. I followed
this example for Vuetify 2.x.
Below is a very simple example of what I did with my code.
As a side note, when I tried to set the wrapper.vm.[dataField] = [value] directly, Jest threw an error not allowing direct set access on the object. In the example.spec.js below, calling wrapper.setData({...}) will allow you to set the data value without any issue.
example.vue:
<template lang="pug">
v-hover(
v-slot:default="{ hover }"
:value="hoverActive"
)
v-card#hoverable-card(
:elevation="hover ? 20 : 10"
)
</template>
<script>
export default {
data() {
return {
hoverActive: false
}
}
</script>
example.spec.js
import { mount, createLocalVue } from '#vue/test-utils'
import Vuetify from 'vuetify'
import example from '#/components/example.vue'
const localVue = createLocalVue()
localVue.use(Vuetify)
describe('example', () => {
const wrapper = mount(example, {
localVue
})
it('should have the correct elevation class on hover', () => {
let classes = wrapper.classes()
expect(classes).toContain('elevation-10')
expect(classes).not.toContain('elevation-20')
wrapper.setData({ hoverActive: true })
classes = wrapper.classes()
expect(classes).not.toContain('elevation-10')
expect(classes).toContain('elevation-20')
})
})
https://github.com/vuejs/vue-test-utils/issues/1421
it('2. User interface provides one help icon with tooltip text', async (done) => {
// stuff
helpIcon.trigger('mouseenter')
await wrapper.vm.$nextTick()
requestAnimationFrame(() => {
// assert
done()
})
})
How can I setup v-model using dynamic keys with v-for? The code below does not update the model.
I am using vuetify framework.
My code is below
Template
<v-card v-for="(value, key) in myData">
<v-checkbox v-model='selected[key]' :name="key" ></v-checkbox>
</v-card>
Script
export default {
props: {
myData : Object
},
data: function(){
return {
selected: {},
active: null,
}
},
methods: {
setModels: function()
{
let data = this.myData;
let sel = this.selected;
Object.keys(data).forEach(function(key){
if(typeof data[key]== 'object')
{
sel[key] = []
}
});
},
},
mounted() {
this.setModels();
}
}
You could create a method:
methods: {
isSelected (key) {
return this.selected.includes(key);
}
}
and set v-model to v-model="isSelected(key)"
try:
<v-card v-for="(item, index) in myData">
<v-checkbox v-model="item" :name="index" ></v-checkbox>
</v-card>
What i dont get is.. Why is your myData a obj and not an array of obj? Then you could do this:
<v-card v-for="item in myData">
<v-checkbox v-model="item.checked" :name="item.name" ></v-checkbox>
</v-card>
My problem is: i have mat-chip list and if i close the first item it's ok and if i close the last item all are close:
this is my html:
<mat-form-field>
<mat-chip-list #chipList>
<mat-chip *ngFor="let keyword of keywords" [removable]="removable" (removed)="remove(keyword)">
{{ keyword }}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input placeholder="{{ 'consultantSearchPage.searchForConsultantOrSkills' | translate }}" [matChipInputFor]="chipList" [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="addSearch($event)">
</mat-chip-list>
</mat-form-field>
and this is my ts:
remove(keyword): void {
const index = this.keywords.indexOf(keyword);
if (index >= 0) {
this._store.dispatch({ type: UPDATE_KEYWORDS, payload: index});
}
}
and if i use:
remove(keyword): void {
const index = this.keywords.indexOf(keyword);
if (index >= 0) {
this.keywords.splice(index, 1);
}
}
it's ok but my data are not update
and this is my reducer code:
export const UPDATE_KEYWORDS = 'UPDATE_KEYWORDS';
.......
case UPDATE_KEYWORDS:
console.log( state.keywords.splice(0, 1));
return Object.assign({}, state, { keywords: state.keywords.splice(action.payload, 1) });
From your comment you are doing this:
case UPDATE_KEYWORDS:
console.log( state.keywords.splice(0, 1));
return Object.assign({}, state, { keywords: state.keywords.splice(action.payload, 1) });
whereas you should do this:
case UPDATE_KEYWORDS:
state.keywords.splice(action.payload, 1);
console.log(state.keywords);
return Object.assign({}, state, { keywords: state.keywords });
You want to use the array that has been spliced, not the array returned from the splice.
https://www.w3schools.com/jsref/jsref_splice.asp