Use a component to render each Vuetify expansion panel item - javascript

I'm trying to using Vuetify's v-expansion-panel component (AKA accordion) to render one panel for each task in a list. The parent component's template is:
<div v-if="tasks.length">
<v-expansion-panels>
<task
v-for="(task, index) in tasks"
:task="task"
:key="index"
/>
</v-expansion-panels>
</div>
Then the child task component renders the content of each panel like so:
<template>
<v-expansion-panel>
<v-expansion-panel-header>
<!-- render panel header content -->
</v-expansion-panel-header>
<v-expansion-panel-content>
<!-- render panel body content -->
</v-expansion-panel-content>
</v-expansion-panel>
</template>
It's convenient for me to render the header and body in a single component, because they largely depend on the same data. However, I don't like the way that the markup for the expansion panels is split over two different components. Because of this, if I unit test the child task component I get a warning:
[Vuetify] The v-expansion-panel component must be used inside a v-expansion-panels
Is there a way I can move all the expansion panel markup into the parent component, but generate the content for v-expansion-panel-header and v-expansion-panel-content in the child task component?

Why not just add all expansion panels together and in task.vue do the v-for?
<task :tasks="tasks" />
Task.vue
<template>
<v-expansion-panels
<v-expansion-panel
v-for="(task, index) in tasks"
:task="task"
:key="index">>
<v-expansion-panel-header>
<!-- render panel header content -->
</v-expansion-panel-header>
<v-expansion-panel-content>
<!-- render panel body content -->
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
</template>

You can have multiple root elements in Vue 2 using render functions. It is more tricky to use but it would work. Take a look at this link.
Alternatively, you can use the vue-fragment component (link to npm page) that does it for you like so:
<template>
<fragment>
<v-expansion-panel-header>
<!-- render panel header content -->
</v-expansion-panel-header>
<v-expansion-panel-content>
<!-- render panel body content -->
</v-expansion-panel-content>
</fragment>
</template>

Related

Iterating slot content for iterated slots in child component in Vue.js

I've got a child component (that I cannot edit) where each row in an object is rendered inside a div with a specific slot, and I'd need to pass data from the parent for each of those elements. I'm trying to iterate through every element of the object in the parent component, generate a slot and pass the desired code to the child, but unfortunately I can't manage to and I can't find any material to support me.
The child component:
<div class="slotchildren" v-for="(child, childindex) in row.elementChildren" :key="childindex">
<span>element nr. {{child.id}}</span>
<slot :name="`element-child-${row[idlabel]}-${childindex}`" :row="child">
...
</slot>
</div>
The parent component (not working):
<template v-for="row in rows"> -->
<template v-slot:`element-row-${row.id}`="{row}">
//--> [vue/valid-v-slot] 'v-slot' directive doesn't support any modifier
//--> [vue/valid-v-slot] 'v-slot' directive must be owned by a custom element, but 'template' is not.
<span>{{row.name}}</span>
</template>
</template>
Is something like this feasible and how? If it's not, what could be a viable workaround, consideind that I can't edit the child component?
Thanks in advance.
I solved it with the following synthax:
<template v-for="row in rows" v-slot:[`element-row-${row.id}`]>
..
</template>

Vue: Mysterious ghost props?

In my project, I came across the following code:
Parent component - <ParameterModal>:
<template>
<modal-wrapper props="...">
<!-- ... other templates similar to this... -->
<template v-else-if="modalTemplate === 'bitmask_set'">
<template slot="header">
<h4 class="center-text">{{title}}</h4>
</template>
<div v-if="errorMessage" class="error-message">
{{errorMessage}}
</div>
<ModalBitmaskSet
v-bind="modalMeta"
:setErrorMessage="setErrorMessage"
:clearErrorMessage="clearErrorMessage"
/>
</template>
<!-- ... -->
<div v-else>
Warning: unmapped modal template!
</div>
<!-- ... -->
</modal-wrapper>
</template>
Ok, cool, this is using a regular slot and named slot to display a component called <ModalBitmaskSet>. So I look inside modal-wrapper to find the outlets...
Child component - <modal-wrapper>
<template>
<!-- some container and wrapper elements and then... -->
<div class="modal-header">
<slot name="header" />
</div>
<div
:class="['modal-body', 'display-flex', 'flex-direction-column', modalTemplate]"
>
<div
id="escape_message"
style="text-align: center; display: none; padding-bottom: 10px;"
>
You have unsaved changes.<br />Please click Save or Cancel to proceed.
</div>
<md-content v-if="modalContent">{{modalContent}}</md-content>
<slot />
</div>
<!-- end containers and wrappers -->
</template>
Also cool, there is where the slots are coming out... but how are props being passed to <ModalBitmaskSet>? When I look in Vue DevTools, I can see that props are somehow being passed to this component that don't exist in the parent. On top of this, when I add new components to <ParameterModal>, they sometimes don't get passed props that other components seem to be getting! This is very weird!
As you can see from the photo, this component is somehow getting passed props that aren't listed in the code! Specifically, the props colIndex, fieldSet, indexOffset, methodIndex and rowIndex in this case, although other components on this <ParameterModal> component appear to get different props.
Am I missing something? Where could these ghostly props be coming from?
This line seems the likely cause, though without seeing the code for modalMeta it's difficult to be sure:
v-bind="modalMeta"
This is using the object v-bind syntax, so whatever properties exist in the modalMeta object will be passed as props to the component.
See:
https://v2.vuejs.org/v2/guide/components-props.html#Passing-the-Properties-of-an-Object
https://v2.vuejs.org/v2/api/#v-bind

How to get multiple views without refreshing the browser via sidebar in vue.js

So what i was trying to do is to be able to use my admin page sidebar navigator (i.e. a side bar that has a few tabs for home, maps, user-profiles). So that say when I am on the 'home' tab and when I click on 'user-profiles' it will update the page but not refresh the browser for the new view..
I was thinking about using v-if on my vue.js views page. so that v-if i select 'user-profiles' tab then use this .vue file as the main component or container.
<template>
<v-container>
<Header />
<AppBarAdmin />
<v-layout class="main">
<NavAdmin class="sidebar" />
<v-flex xs12 class="display_container">
<Dashboard class="dashboard" />
</v-flex>
</v-layout>
</v-container>
</template>
so what i was thinking was being able to use a v-if statement on the <Dashboard class="dashboard"> line so that the logic might be 'if clicked on Dashboard' then display this vue file

Vuejs - Passing parent component props to child element with the template

The problem I'm trying to solve is I want the child component to react when a v-expansion panel is expanded/contracted.
Normally this would be trivial, however, I'm attempting to pass a value from a prop within a vuetify component to component via a scoped slot. Because I'm rendering the child components within a loop, I can't just use Data to bind the prop.
<v-expansion-panel expand>
<v-expansion-panel-content
v-for="item in this.items"
:key="item.key">
<div slot="header">
content
</div>
<slot :items="item.children"></slot>
</v-expansion-panel-content>
</v-expansion-panel>
The v-expansion-panel-content has a prop called value. I need to bind that prop to the slot. Ideally, I'd like to achieve something like this:
<slot :items="item.children" :panelValue="value"></slot>
Any ideas would be greatly appreciated.
I've solved this, but in a less than ideal way.
<v-expansion-panel expand>
<v-expansion-panel-content
v-for="item in this.items"
:key="item.key"
v-model="item.isOpen">
<div slot="header">
content
</div>
<slot :items="item.children" :isVisible="item.isOpen"></slot>
</v-expansion-panel-content>

Modify sidebar component based on content section component in Vue

Here is my template section:
<template>
<!-- START Home.vue section -->
<div class="container-fluid" v-if="user.authenticated">
<!-- START Sidebar.vue section -->
<sidebar-component></sidebar-component>
<!-- END Sidebar.vue section -->
<div class="main-container">
<!-- START Nav.vue section -->
<nav-component></nav-component>
<!-- END Nav.vue section -->
<!-- START Content Section -->
<router-view></router-view>
<!-- END Content Section -->
</div>
</div>
<!-- END Home.vue section -->
</template>
Clearly it has 3 components: NavComponent,SidebarComponent, Main component area that will be replaced by the routed component.
I have another component ApplicationList component that when will replace the router-view section of above template, is supposed to cause the sidebar component to behave differently(say for example sidebar becomes hidden or its background becomes red).
So how do I do it?
Install https://github.com/vuejs/vuex-router-sync . It will give you your current route state in your Vuex.
In your <sidebar-component> just refer to this.$store.state.route.name (or path, or whatever you need) and use it in your v-if or some watch .
This is the easiest and most scalable solution to handle changes in components based on current route in application.
Hope it helps.

Categories

Resources