Vue js Vuetify custom component not rendered based on the display breakpoint - javascript

I have a custom component that basically is a v-btn component with specific style. When I'm using it inside v-menu activator with conditional based on display breakpoint, the custom component does not display on the screen. But if I use regular v-btn the button displays properly based on the display breakpoint. What am I doing wrong here?
https://codepen.io/jgunawan-dc/pen/XWzJqRy?editors=1010
<div id="app">
<v-app id="inspire">
<div class="text-center">
<v-menu offset-y>
<template v-slot:activator="{ on, attrs }">
<global-custom-button
v-if="$vuetify.breakpoint.mdAndDown"
v-bind="attrs"
v-on="on"
>
Show on medium and lower
</global-custom-button>
<v-btn v-else
color="primary"
dark
v-bind="attrs"
v-on="on"
>
Dropdown
</v-btn>
</template>
<v-list>
<v-list-item
v-for="(item, index) in items"
:key="index"
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
</v-app>
</div>
Vue.component('global-custom-button', {
template: '<v-btn outlined color="info" #click="$emit(\'click\', $event)"><slot></slot></v-btn>'
});
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
items: [
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me 2' },
],
}),
})

The are two errors arising from the above scenario:
Error message 1:
[Vue warn]: Error in nextTick: "NotFoundError: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node."
Error message 2:
DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
This arises when Vue tries to insert an element before another one, but the element no longer exists in the DOM.
In your case, it seams that changing $vuetify.breakpoint.mdAndDown from true to false or vice-versa cleans the global-custom-button component or the Dropdown v-btn.
One possible workaround would be to use v-show instead of v-if.
A caveat:
Note that v-show doesn’t support the element, nor does it work with v-else.
So this suggestion can work (you can change to fit your needs):
<global-custom-button
v-show="$vuetify.breakpoint.mdAndDown"
v-bind="attrs"
v-on="on"
>
Show on medium and lower
</global-custom-button>
<v-btn v-show="!$vuetify.breakpoint.mdAndDown"
color="primary"
dark
v-bind="attrs"
v-on="on"
>
Dropdown
</v-btn>
EDIT:
This works without v-if or v-show

For a quick fix, use v-show as #Rotiken said.
As another solution you could also register two global components and show/hide it using extra condition prop:
<v-menu offset-y>
<template v-slot:activator="{ on, attrs }">
<global-custom-button
:condition="$vuetify.breakpoint.mdAndDown"
v-bind="attrs"
v-on="on"
>
Show on medium and lower
</global-custom-button>
<global-custom-button-2
:condition="!$vuetify.breakpoint.mdAndDown"
v-bind="attrs"
v-on="on"
>
Dropdown
</global-custom-button-2>
</template>
...
</v-menu>
...
Vue.component('global-custom-button', {
template: '<v-btn v-if="condition" outlined color="info" #click="$emit(\'click\', $event)"><slot></slot></v-btn>',
props: ['condition']
});
Vue.component('global-custom-button-2', {
template: '<v-btn v-if="condition" color="primary" dark #click="$emit(\'click\', $event)"><slot></slot></v-btn>',
props: ['condition']
});
...
According to this CodePen, this also works.
Why this happens and why v-show works fine with same template: I can't give an exact answer, but there are some assumptions.
Vue.js docs says that v-if removes objects from DOM, whereas v-show changes its display state in CSS, but keep it in DOM.
Since you are using v-if in a vuetify v-menu component, perhaps the component has some methods for updating the v-slot:activator content that can conflict (and not performed at the same time) with conditional rendering using v-if and vuetify display breakpoints.
If you are familiar with TypeScript, you can look into v-menu sources or into activatable mixin sources. Maybe here you will find the true reason for this behavior.
If you just want to avoid such problems, use v-show in such cases.

Related

what does {on, attrs} make in vue/vuetify?

I am aware that this question has been asked already but I still struggle to understand what does it mean?
<v-dialog
v-model="dialog"
width="500"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
color="red lighten-2"
dark
v-bind="attrs"
v-on="on"
>
Click Me
</v-btn>
</template>
(that is taken from official vuetify documentation https://vuetifyjs.com/en/components/dialogs/#usage , but I suspect there is just a JS thing that I don't understand).
What does {on, attrs} mean and how they propagate down to v-btn where they are used in v-on and v-bind?
There a few concepts here that need to be explained.
"Scoped slots" (or just "slots" since Vue 3) can pass data to the parent component. Think of the slot as kind of like a function with arguments. In this case the activator slot of <v-dialog> is passing an object containing properties on and attrs for use inside the slot content template. You should refer to the <v-dialog> docs to know how this data should be used; in this case <v-dialog> needs to know when the activator is clicked in order to present the dialog, which is why it exposes on and attrs that you need to link to whatever custom button you designate to be the dialog activator.
In a lot of component libraries, it's common for slots to expose on (event listeners) and attrs (attributes/props) that the component requires you to set on a component in the template; for this you use v-on and v-bind respectively. Refer to the docs to see how these directives can be used in this way to bind multiple attributes at once:
<!-- binding an object of attributes -->
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
This specific syntax (object destructing):
<template v-slot:activator="{ on, attrs }">
<v-btn v-bind="attrs" v-on="on">
is the same as this:
<template v-slot:activator="scope">
<v-btn v-bind="scope.attrs" v-on="scope.on">
The on and attrs properties are just being extracted into separate variables.

Only make the buttons inside v-list clickable, while disabling the v-list-item content

I'm using a v-list component to show available bundles of a product. Basically, I want to be able to press a button in the v-list to remove this bundle (the item on the row of the v-list).
Clicking on the button works fine, but the entire v-list-content is clickable. Even tho it does nothing, I want to disable it so that the user can only click on the button to the right of every v-list-item. Is there a way to do that?
I'm aware that I can add the props disabled to the v-list tag. However, every child tags of the v-list are disabled, so the button becomes unclickable.
<v-list rounded> <!-- disabled -->
<v-list-item-group
v-model="selectedItem"
color="primary"
>
<v-list-item
v-for="(bundle, i) in editForm.suggested_bundles"
:key="i"
>
<v-list-item-content append-icon="mdi-delete">
<v-list-item-title v-text="bundle"></v-list-item-title>
</v-list-item-content>
<v-list-item-icon>
<v-btn #click="removeBundle(editForm.suggested_bundles, bundle)">
<v-icon small>mdi-delete</v-icon>
</v-btn>
</v-list-item-icon>
</v-list-item>
</v-list-item-group>
</v-list>
Any solution for this?
Thanks in advance!
Looks to me that you don't perhaps need the v-list-item-group, so by removing that, only your button is clickable:
<v-list-item v-for="(bundle, i) in editForm.suggested_bundles" :key="i">
<v-list-item-content append-icon="mdi-delete">
<v-list-item-title v-text="bundle"></v-list-item-title>
</v-list-item-content>
<v-list-item-icon >
<v-btn #click="removeBundle(editForm.suggested_bundles[i])">
<v-icon small>mdi-delete</v-icon>
</v-btn>
</v-list-item-icon>
</v-list-item>
If you need to use selectedItem you can always (re)assign the value in removeBundle function. Hopefully this solution will suit your needs.
DEMO for reference

How bind vuetify skeleton-loader to v-image load event

My target is to build a conditional rendering between <v-skeleton-loader> and <v-card>Skeleton binding them to a property called isActive
I want that Card component only renders when the <v-image> has been loaded.
"Officially" based on Vuetify's guideline, the <v-image> component has its own "load" event based on this sample codepen, this could be easilly handled by calling the loadCard(item) and setting isActive=true after image has been loaded <v-img :src="item.img" #load="loadCard(item)" > but it doesn't trigger on #load event.
<div v-for="item in items" :key="item.id" >
<v-card v-if="item.isActive">
<v-img :src="item.img" #load="loadCard(item)" >
......
</v-card>
<v-skeleton-loader v-else
:loading="item.isActive"
style="width:13.020vw; margin-bottom:2.395833vw;"
transition="scale-transition"
type="card">
</v-skeleton-loader>
I've tried to put call the function into the skeleton loaded by #click="loadCard(item)" and it works perfectly. After the click the Card renders.
<v-skeleton-loader v-else
#click="loadCard(item)"
:loading="item.isActive"
style="width:13.020vw; margin-bottom:2.395833vw;"
transition="scale-transition"
type="card">
</v-skeleton-loader>

v-menu: v-text-field in activator slot disappears when pushing route in nuxt project

In my nuxt project i use Vuetify. I take date from users and when user changed page by clicking save button, v-text-field dissapears before page changed, before going next page . It is because of route changing but i could not find a solution for this problem.
<v-menu v-model="menu"
:close-on-content-click="false"
transition="scale-transition"
rounded
nudge-bottom="60px"
min-width="290px">
<template v-slot:activator="{ on, attrs }">
<v-text-field :value="formatDateText"
label="Birth Date"
v-bind="attrs"
v-on="on"
offset-y
hide-details>
</v-text-field>
</template>
<v-date-picker no-title
v-model="date"
#change="dateChangeFormat(formatDate)"
/>
</v-menu>
I found 2 solutions for this problem . first solution is use
features: {
transitions: false
},
inside the nuxt.config.js
Second solution : add ref to v-menu .
<v-menu v-model="menu"
ref="menu"
:close-on-content-click="false"
transition="scale-transition"
rounded
nudge-bottom="60px"
min-width="290px">
in script area ;
beforeDestroy() {
this.$refs.menu.activatorNode = null;
},

What is the purpose of <template> usage in Vuetify?

I want to use Vuetify 2.0 in my project and currently reading about v-stepper component which is used to display progress through numbered steps.
In the playground example provided I see that they are using <template> element to wrap content of v-stepper component. HTML looks something like this (note: I removed unnecessary details):
<v-stepper>
<template v-for="n in steps">
<v-stepper-content
:key="`${n}-content`"
:step="n"
>
</v-stepper-content>
</template>
</v-stepper>
Notice the <template> tag used. What is the purpose of it? When should I use it as opposed to just putting the <v-stepper-content> directly inside of <v-stepper>?
I've read a bit about element on MDN but I am not sure how to use it specifically with Vuetify (or more generally with Vue.Js or just pure HTML/CSS/JS for that matter).
a <template> in the context of a v-for loop is an organizational item.
It does not get rendered by the browser. It is there to help with more complex rendering situations, where you don't want to limit yourself to a single element
In most cases you have a pretty straight forward mapping of items, each item in an array gets a <li> element. If this is the case, you're not likely to use this.
Here is an example of a problem where it might help...
Let's say you want to loop through an array of objects, and render a v-btn if the object is a button, and a v-image if the object is an image.
without template...
<span v-for="item in items">
<v-btn v-if="item.isBtn"></v-btn>
<v-img v-else-if="item.isImg"></v-img>
</span>
The problem is that each item will be wrapped in the span.
<span>
<v-btn/>
</span>
<span>
<v-img/>
</span>
<span>
<v-btn/>
</span>
If you, however, use the template element, the wrapping element is no longer there.
<template v-for="item in items">
<v-btn v-if="item.isBtn"></v-btn>
<v-img v-else-if="item.isImg"></v-img>
</template>
and you will get...
<v-btn/>
<v-img/>
<v-btn/>
You can also have it return multiple items in one instance of the loop.
in the vue docs at https://v2.vuejs.org/v2/guide/list.html#v-for-on-a-lt-template-gt
it shows an example of rendering more than one item per iteration:
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
There are some other cases where this might be helpful but not likely something you come across on a daily basis.
TL;DR;
Vue doesn't render the <template> element. It helps organize code chunk without the need of a single child element when looping
part 2.
When should I use it as opposed to just putting the directly inside of ?
Because the structure of vertical and horizontal steppers is different, the vuetify authors used it in the playground to allow users to toggle it. The first level of template (<template v-if="vertical">) is used do determine whether the next level should render the v-stepper-step elements as vertical or as horizontal. The second level is used to do the iterating of items.
example:
vertical (step and content are siblings):
<template>
<v-stepper v-model="e6" vertical>
<v-stepper-step :complete="e6 > 1" step="1">
Select an app
<small>Summarize if needed</small>
</v-stepper-step>
<v-stepper-content step="1">
<v-card color="grey lighten-1" class="mb-12" height="200px"></v-card>
<v-btn color="primary" #click="e6 = 2">Continue</v-btn>
<v-btn text>Cancel</v-btn>
</v-stepper-content>
<v-stepper-content step="2">...</v-stepper-content>
<v-stepper-step :complete="e6 > 3" step="3">...</v-stepper-step>
</v-stepper>
</template>
horizontal (each step is separate):
<template>
<div>
<v-stepper>
<v-stepper-header>
<v-stepper-step step="1">Select campaign settings</v-stepper-step>
<v-divider></v-divider>
<v-stepper-step step="2">Create an ad group</v-stepper-step>
<v-divider></v-divider>
<v-stepper-step step="3">Create an ad</v-stepper-step>
</v-stepper-header>
</v-stepper>
<v-stepper value="2" class="mt-12">
...
</v-stepper>
<v-stepper value="3" class="mt-12">
...
</v-stepper>
</div>
</template>

Categories

Resources