render a Vue slot in a layout from component - vue.js

I'm having problems with a named slot. This seems like it should work. In the code below I'm trying to use a named slot "sidebar". I would expect my sidebar slot content to show up between the Sidebar starts and Sidebar ends text but nothing shows up in that slot. Everything renders in the main slot.
Here's my code.
route...
{
path: "/test",
name: "test",
meta: {
layout: "test-layout"
},
component: () =>
import("#/pages/Test.vue")
},
and App.vue template...
<template>
<div id="app">
<component :is="layout">
<router-view />
</component>
</div>
</template>
and test-layout...
<template>
<div>
<div>
<h1>Sidebar starts</h1>
<slot name="sidebar"/>
<h1>Sidebar ends</h1>
</div>
<div class="container">
<h1>Content starts</h1>
<slot/>
<h1>Content ends</h1>
</div>
</div>
</template>
and page Test.vue...
<template>
<test-layout>
<span slot="sidebar">sidebar slot content {{forecast.todaySummary}}</span>
<div>main slot content {{forecast.currentSummary}}</div>
</test-layout>
</template>
<script>
import api from "#/js/web-services";
export default {
data() {
return {
forecast: null
};
},
created() {
api.getDailyForecast().then(response => {
this.forecast = response.data;
});
}
};
</script>
and the import in my main.js
import TestLayout from "./layouts/test-layout.vue";
Vue.component('test-layout', TestLayout);
Why isn't my sidebar slot working?
UPDATE
If I get rid of the two lines in main.js and add
import TestLayout from "#/layouts/test-layout.vue";
and
export default {
components: { TestLayout },
data() {...
to Test.vue then it works.

In your router file you are using layout: "test-layout" this means what ever comes in your vue component will be rendered in base test-layout.
There are two ways as far as I know to render the layouts.
Do not define layout in router file and on parent component define named slots like this<slot #header></slot><slot #body></slot> then every slot will be rendered within this (test-layout) layout, and then in your each component extend like this <test-layout><template header>Header content</template><template body>Body content</template></test-layout>.
Defining layout in router file like you did, you can not further use in slots in that layout, you can just import other components e.g <template><Component1><Component2> </template>

Related

Vue: Reuse loading spinner template and logic

I've multiple (20+) pages where I need the following code:
<template>
<template v-if="isLoading">
<Spinner full size="medium" />
</template>
<template v-else>
<p>Page data</p>
</template>
</template>
<script>
export default {
computed: {
isLoading() {
return this.$store.getters['loader/isLoading'];
},
},
};
</script>
I don't want to rewrite this part everytime I need it so is there a way to create something like a higher order component with access to the computed method and a way to add the hoc to the script tag of the different vue files? Or another way to archive this?
I could recommend extending the spinner component where you want to show the spinner. I've created a boilerplate setup that show a very simple implementation of this approach here.
The main idea is to expose a default slot for you spinner component, and wrap the page component in that slot.
<template>
<div>
<Spinner v-if="isLoading" full size="medium" />
<!-- Slot for component data -->
<slot v-else></slot>
</div>
</template>
<script>
export default {
computed: {
isLoading() {
return this.$store.getters['loader/isLoading'];
},
},
};
</script>
Then in any component that you want to show the spinner:
<template>
<spinner>
<!-- Pass here the component content to be shown after the spinner is hidden -->
</spinner>
</template>
<script>
import Spinner from "./spinner";
export default {
name: "Page1",
extends: Spinner,
components: { Spinner }
};
</script>

Why Vue 2 remove every thing when using :is prop override component?

Please see this minimum example, I have a simple component called HelloWorld.vue
HelloWorld.vue
<template>
<div class="foo">bar</div>
</template>
When I using this component like this
App.vue
<template>
<HelloWorld />
</template>
<script>
import HelloWorld from "./HelloWorld.vue";
export default {
components: {
HelloWorld,
},
};
</script>
This rendered HTML looks like this
Rendered HTML
<div class="foo">bar</div>
However, when I add :is prop, rendered HTML changed
App.vue
<template>
<HelloWorld is="h2" />
</template>
<script>
import HelloWorld from "./HelloWorld.vue";
export default {
components: {
HelloWorld,
},
};
</script>
Rendered HTML
<h2></h2>
Why is this happening?
Is it possible to overwrite only the outer HTML tag just like the class and style prop?
is should be used together with the component element:
<component is="h2"></component>
<HelloWorld is="h2" /> efficiently renders h2 instead of HelloWorld.
In order for root element to be configurable, the component should provide this:
<template>
<component :is="tag" class="foo">bar</div>
</template>
<script>
export default {
props: {
tag: {
type: String,
default: 'div'
}
}
}
</script>

Vue: How to supply the layout component with dynamic props depending on router-view component's data

I'd like to have a layout component with a header component and a <router-view>, but the header component should receive different dynamic props depending on the data of the router-view component.
I'm agnostic to the way in which the layout architecture is being achieved: Either with nested routes or using a slot and passing the layout name via the route's meta field.
So, the header component should be passed in a dynamic prop from the view component - let's say Home.vue: If we go to route /home the router-view will be Home.vue and in Home.vue we have a reactive data field (i.e. scrolledDown) and the prop passed to the header component (i.e. theme) should depend on the value of this data field (scrolledDown).
The easiest way I can think of is using local state management (vuex or whatever), so that the view components (i.e. Home.vue) can store the reactive data and the layout component can grab it and pass it as prop to the header component. But maybe there are much better ways to accomplish this?
MainHeader.vue:
<template>
<div :class="`header--${theme}`">...</div>
</template>
<script>
export default {
props: ["theme"]
}
</script>
Layout.vue:
<template>
<div>
<main-header />
<router-view />
</div>
</template>
<script>
import MainHeader from "../MainHeader";
export default {
components: { MainHeader }
}
</script>
or alternatively:
App.vue:
<template>
<component :is="layout">
<router-view />
</component>
</template>
// script with `layout` data prop and layout component imports
Layout.vue:
<template>
<div>
<main-header />
<slot />
</div>
</template>
<script>
import MainHeader from "../MainHeader";
export default {
components: { MainHeader }
}
</script>
Home.vue:
<template>
<div>...</div>
</template>
<script>
export default {
computed: {
scrolledDown() { ... }
}
}
</script>

Vuejs import component in another component

I created a dashboard.vue component and I imported table.vue component into it but the table.vue doesn't appear in my web page and in the vue.dev tools.
When I import the table.vue in app.vue there's no issue.
Below my files.
Thanks in advance!
//dashboard.vue
<template>
<div>
<table></table>
</div>
</template>
<script>
import table from "./table";
export default {
name: 'Dashboard',
component: {
table,
}
}
</script>
<style >
</style>
//table.vue
<template>
<div>
<p>Test</p>
</div>
</template>
<script>
export default {
name: 'Table',
}
</script>
You cannot name components the same as reserved HTML elements. Change it to my-table.
You'll need to map it:
components: {
'my-table': table,
}

How to access parents data value in vuejs with the template syntax

In my VueJS app I am using bootstrap-vue and want to use iframe inside a collapsable elements b-collapse. Because of some problems with iframe content and resizing (problem not related here) I found out that if I enable/disable b-embed with conditional rendering it works.
The parent component b-collapse has a data element called show which change its state if toggle is clicked. In my HelloWorld component I want that b-collapse can pass it's show value into the if check of b-embed.
My approach with this.$parent.$data.show isn't working and I am not sure if there is any better way to do so.
<template>
<div>
<b-btn v-b-toggle.logs>
<span class="when-opened">Close</span>
<span class="when-closed">Open</span>
Trace
</b-btn>
<b-collapse id="logs">
<b-embed :src="src" v-if="this.$parent.$data.show"></b-embed>
<div>Data: {{this.$parent.$data.show}}</div>
</b-collapse>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import { Prop, Component, Inject } from "vue-property-decorator";
#Component
export default class HelloWorld extends Vue {
src = "http://localhost:7681/";
}
</script>
Like this:
parent.vue
<template>
<Child :show="myShow"></Child>
</template>
<script>
import Child from './child'
export default {
data () {
return {
myShow: 'StackOverflow'
}
},
components: {Child}
}
</script>
child.vue
<div>
<b-btn v-b-toggle.logs>
<span class="when-opened">Close</span>
<span class="when-closed">Open</span>
Trace
</b-btn>
<b-collapse id="logs">
<b-embed :src="src" v-if="this.$parent.$data.show"></b-embed>
<div>Data: {{this.$parent.$data.show}}</div>
</b-collapse>
</div>
<script>
export default {
props: {
show: {
type: Number,
require: true
}
}
}
</script>
Or use vuex to do this.