Vue scoped slot child not rendering slot content - vuejs2

I think I've stumbled over something really strange. I have a Menu component which has two ways of managing visibility of a dropdown div:
Internally using local state;
Using v-model to keep track on the parent component.
I also have a child component called MenuItem. This MenuItem (one or more) is to be passed into the Menu default slot. The MenuItem has a slot of its own, for an icon.
The Menu also has a slot for the "activator", an element which toggles the menu visibility. This can be a regular slot, or a scoped slot.
If the visibility of the menu is handled through the scoped slot:
<Menu>
<template v-slot:activator="{ on }">
<button v-on="on" type="button">Click me</button>
</template>
</Menu>
Then the icon for MenuItem will render the first time the menu is visible, but subsequent changes to visibility won't render the icon. The icon slot is optional, so I am checking for it's presences using this.$slots['icon-left'], and after the first render this is undefined.
However, if I manage the visibility on the parent component using v-model, this behaviour is not present.
<Menu v-model="isMenuVisible">
<template v-slot:activator>
<button #click="isMenuVisible = !isMenuVisible" type="button">Click me</button>
</template>
</Menu>
I am using v-if for toggling the visibility of the dropdown menu. This behaviour is not present if I use v-show. This behaviour is also not present if I do not check for the existence of this.$slots['icon-left'].
I have a full demo of the issue.
Basically I am very confused as to why this is happening, and I don't even know what to Google.

Related

v-edit-dialog inside v-data-table restores original value when closed

I have a Vue application that is using Vuetify. For some reason the v-chip component is not re-rendering when the binded value is changed.
<v-data-table>
<template v-slot:item.active="{ item }">
<v-edit-dialog :return-value.sync="item.active">
<v-chip :key="item.active" outlined :color="item.active ? 'success' : 'error'">{{ item.active ? "Open" : "Closed" }}</v-chip>
<template v-slot:input>
<v-switch
#change="saveRowField(item.id, 'active', item.active)"
v-model="item.active"
:true-value="1" :false-value="0"
color="success" label="Open"
></v-switch>
</template>
</v-edit-dialog>
</template>
</v-data-table>
The v-switch in the v-edit-dialog correctly updates the binded field item.active. However the v-chip component inside the template does not rerender on state change.
The :key attribute is binded to the same field as the v-switch.
Why is the v-chip not re-rendering when the value binded to the key is changed?
Tried your code and re-rendering content of v-chip is not an issue. What I see is chip is changed once I click v-switch but when v-edit-dialog is closed, the original value is restored.
So the problem is in v-edit-dialog. If you put large prop on it, it will display buttons - "Save" and "Cancel". If you use "Save" button, the value is saved. If "Cancel", original value is restored.
Without buttons, only way to close the dialog is clicking "away", which is interpreted as "Cancel" by v-edit-dialog and thus original value is restored...
Possible solutions:
Either let user use "Save"/"Cancel" buttons to confirm/cancel editing
Or remove :return-value.sync="item.active" (responsible for this Save/Cancel behavior).
Demo

Why do not undeclared props pass to the components's root element?

I made a wrapper around Vuetify's 'v-dialog' component named 'Modal' and wanna pass props through it:
<template>
<v-dialog
class="modal"
max-width="600"
v-on="vDialogListeners"
>
<v-card>
<v-card-title class="headline">
<slot></slot>
</v-card-title>
<v-card>
...
</v-dialog>
</template>
I use it like this ('value' prop):
<Modal
:value="showModal"
:confirm="true"
#close="onModalClose"
#click:outside="onModalClose(false)">
Are you sure you'd like to log out?
</Modal>
I know about $attrs and it works if I bind it explicitly, i.e, v-bind="$attrs" within <v-dialog> opening tag.
My question: why doesn't work it by default, that's without explicit v-bind="$attrs", if <v-dialog> is the component's root element and should accept all props undeclared in the component, or I'm wrong and misunderstand this part of documentation?
I used to think that v-bind="$attrs" is only used in case inheritAttrs: false in order to pass udeclared props to a component's not-root element (a nested one).
I have a guess: maybe, it only concerns the components root html element (e.g., input tag) by default.
It seems I figured out the proper usage of it. By default, it adds these $attrs to the DOM element as attributes. By explicit binding, I can pass them to the necessary child component. By setting inheritAttrs = false, I cancel the default behavior of adding attributes to the DOM element. Thus, I can combine these options to get the suitable behavior.

Pass a button with click handler via slot to recursive child component

I've got a page template with the following code part:
<nested-draggable v-bind:list="list" v-bind:selected="selected" v-bind:group="dragGroup">
<slot>
<v-icon v-on:click="$root.$emit('click', el)" small v-if="allowcreate" style="float: right">mdi-plus</v-icon>
</slot>
</nested-draggable>
the sub component ("nested-draggable.vue") for the recursion looks like this:
<template>
<ul class="tree">
<draggable
class="dragArea"
tag="li"
v-for="el in list"
v-bind:elementdata="el"
v-bind:key="el._id"
v-bind:list="list_empty"
v-bind:selected="selected"
v-bind:group="group"
v-on:add="add"
>
<span v-bind:class="{'selected' : el._id === selected._id}" v-on:click="elemClicked(el)">{{ el.title }}</span>
<slot></slot>
<!-- render children of the current iterated element -->
<nested-draggable
v-bind:list="el.children" v-bind:selected="selected" v-bind:group="group">
<!--<slot></slot>-->
</nested-draggable>
</draggable>
</ul>
</template>
so I'd like to have the click event from the button within the passed slot emited with the current iteration's var "el" when the "plus" button is clicked, but within the slot the "el" var that is used within the iteration at the nested-draggable component can not be accessed. Vue tells that there is no "el" reference when trying to emit. (Throwing this error: https://pastebin.com/8bNwMcDr)
So how can I access the recursive data within the passed slot? How do I have to define my slot when passing it?
The only solution I found is putting the button/event-link directly into the nested-draggable component (not as slot) but I think to be clean and write a nice separated component, this would not belong into the nested draggable component, but in its parent.
You don't need to pass your event from the template because you can get in your method anyways. This should help you out.

How to I change a v-btn's state (visually) so that it's selected/deselected in a template?

By default, Vuetify's v-btn has a "selected state", which from what I can tell, is just a darkened background. I'm using a few v-btns in a v-app-bar. One of these buttons is a Home button. When the vue app is launched (i.e. main route), I want to set the Home button as selected so that the user knows that this is the home page. Is there a simple way that I can do this from the template that I have my v-app-bar in?
<template>
<v-app-bar app fixed>
<router-link to="/">
<v-img src="/assets/my-logo.png" to="/home" class="mx-2" max-height="128" max-width="128" contain/>
</router-link>
<v-btn to="/home" tile>Home</v-btn>
<v-btn to="/another-view" tile>Another View</v-btn>
<v-btn to="/yet-another-view" tile>Yet Another View</v-btn>
</v-app-bar>
</template>
So given the markup above, how can I set the Home button as "active" or "selected" when the page opens up at the default route?
In the v-btn documentation, there's a prop called "input-value" which says that it controls the button's active state. The problem is that its type is "Any", so I'm not sure how to use it (and the documentation doesn't show anything about it). I tried setting to true/false and nothing changes.
Also, if I want to just get rid of all the buttons active states, how do I do that?
Why isn't there a simple solution like focused="true|false"?

Prevent on click on parent when clicking button inside div

Is it possible to prevent the function on the <div> element from running when clicking the button inside the div?
When clicking the button element, the function: toggleSystemDetails should not be triggered? Is this possible in Vue?
<div v-on:click="toggleSystemDetails($event, system.id)" v-for="(system, index) in organization.systems" :key="system.id">
Outer Div
<button v-on:click="toggleTileOptionsMode($event, system.id, system.name, system.layout)">
Inner Button
</button>
</div>
Have a look at Event Modifiers in the Vue.js v3 docs (v2 docs here), v-on:click.stop will stop that click from propagating or "bubbling" up to the parent element.
Here is how to master this problem.
Say you have a parent element and some child elements.
1.(1st case) You want the parent click to do not affect the child clicks. Just put at the parent element the .self modifier:
<div class="parent" #click.self="parent"> <!-- .self modifier -->
<span class="child" #click="child1">Child1</span>
<span class="child" #click="child2">Child2</span>
<span class="child" #click="child3">Child3</span>
</div>
See it in action here
note: if you remove the .self when you click a child, the parent event will fire too.
2.(2nd case) You have the below code:
<div #click="parent">
Click to activate
<i class="fa fa-trash" title="delete this" #click="delete_clicked"></i>
</div>
The problem is:
When you click the icon element the parent click will fire. (you don't want this)
You can NOT use the 1st solution because you want to fire the parent event even if the text "Click to activate" gets clicked.
The solution to this is to put the .stop modifier to the icon element so the parent event will not fire.
See it in action here
as mentioned on the link provided by Justin
you can .self in the click event
<!-- only trigger handler if event.target is the element itself -->
<!-- i.e. not from a child element -->
<div v-on:click.self="doThat">...</div>
I just want to put my two cents here, since I do find myself looking for this problem again and again (one day I will remember).
I have found in some instances, the child element needs #click.stop.prevent and that's it, to stop it from bubbling to the parent.
I assume v-on:click.stop.prevent would have the same effect (non-shorthand).
additionally, if a child element detains you from clicking the parent element, you can fix this by adding CSS to the child element pointer-events: none.
this solution is valid if there is no event in the child element.
You can use #click.stop on child component, for examaple
.modal(#click="myfunc")
default-content(#click.stop)