Vue.js render multiple slots - javascript

I have the following app:
<template>
<component :is="layout">
<router-view :layout.sync="layout"/>
</component>
</template>
<script>
import LayoutBlank from './LayoutBlank'
export default {
name: 'app',
data() {
return {
layout: LayoutBlank,
};
},
}
</script>
<script>
import LayoutBlankTemplate from './LayoutBlankTemplate'
export default {
name: 'LayoutBlank',
created() {
this.$parent.$emit('update:layout', LayoutBlankTemplate);
},
render() {
return this.$slots.default[0];
},
}
</script>
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'LayoutBlankTemplate',
}
</script>
<template>
<layout-blank>
Test content
</layout-blank>
</template>
<script>
import LayoutBlank from './LayoutBlank';
export default {
name: 'BlankTest',
components: {LayoutBlank},
}
</script>
All works fine. But now I would like to add one more slot to the LayoutBlankTemplate:
<template>
<div>
<slot></slot>
<slot name="second"></slot>
</div>
</template>
<script>
export default {
name: 'LayoutBlankTemplate',
}
</script>
and use it in BlankTest:
<template>
<layout-blank>
<template #default>Test content</template>
<template #second>Second test content</template>
</layout-blank>
</template>
<script>
import LayoutBlank from './LayoutBlank';
export default {
name: 'BlankTest',
components: {LayoutBlank},
}
</script>
The code renders only the default slot content. The problem is that I'm using the return this.$slots.default[0]; in LayoutBlank component which renders only the default slot content.
I cannot find the way how to render all slots in LayoutBlank::render() method. I know that I'm able to get the slots list using this.$slots or this.$scopedSlots but I can't find the way how to pass them to the LayoutBlankTemplate to make them render.

You could put the slots into elements you create I believe. This is mentioned (briefly) in the render function documentation.
This may not be exactly the layout you want, but for example,
<script>
import LayoutBlankTemplate from './LayoutBlankTemplate'
export default {
name: 'LayoutBlank',
created() {
this.$parent.$emit('update:layout', LayoutBlankTemplate);
},
render(createElement, context) {
return createElement('div', {}, [
createElement('div', {}, this.$slots.default),
createElement('div', {}, this.$slots.second)
])
},
}
</script>

Related

Provide a ref to inject it in a child component with Vue js

Anybody knows how to provide a ref to child components. For example:
Parent component provide the ref:
<template>
<div ref="myRef" />
</template>
<script>
export default {
name: 'SearchContainer',
provide: {
parentRef: this.$refs.myRef
}
}
</script>
</style>
And Child component inject it:
<template>
<div />
</template>
<script>
export default {
name: 'SearchCard',
inject: ['parentRef']
}
</script>
</style>
Generally the interaction between parent and child component should be done using props and emitted events :
<template>
<div />
</template>
<script>
export default {
methods:{
changeParentStyle(){
this.$emit('change-style')
}
}
}
</script>
</style>
parent :
<template>
<child #change-style="changeStyle" />
</template>
<script>
export default {
name: 'SearchContainer',
methods:{
changeStyle(){
//change the property that affects your style
}
}
}
</script>
</style>
I have manage to achieve providing ref to children. But in my opinion if you want to communicate betwwen parent and children I think you have got your answer already by Boussadjra Brahim.
Still providing ref has its own use cases. I am usinig it for dialogs.
Here is my solution:
<!-- Parent Component -->
<template>
<div ref="myRef"></div>
</template>
<script>
export default {
name: 'SearchContainer',
methods:{
getMyRef(){
return this.$refs.myRef
}
},
provide() {
return{
parentRef: this.getMyRef
}
}
}
</script>
<!-- Child Component -->
<template>
<div></div>
</template>
<script>
export default {
name: 'SearchContainer',
methods:{
useRef(){
this.parentRef().data // Can access data properties
this.parentRef().method() // can access methods
}
}
inject: ['parentRef']
}
</script>

Vue / Nuxt access function from global component

I have a globally registered component (modal) in my NUXT app default layout which I would like to be able to trigger a method from any other component on a click event. I had it initially setup by importing the component into each page but I would rather this be a global component and not have to import explicitly on each page and just have one register call in the default layout. Is this achievable my using $root.$emit? or should I look into a vue store?
Default Layout:
<template>
<div class="layout-wrapper">
<Nuxt />
<Modal />
</div>
</template>
<script>
import Modal from "~/components/Modal.vue";
export default {
name: "DefaultLayout",
components: {
Modal
}
};
</script>
Modal:
<template>
<div>
<el-dialog :visible.sync="modal">
<div class="dialog-content">
<p>
Modal Content
</p>
</div>
</el-dialog>
</div>
</template>
<script>
import { Dialog } from "element-ui";
export default {
name: "Modal",
components: {
"el-dialog": Dialog
},
data() {
return {
modalOne: false
};
},
methods: {
openModal() {
this.modalOne = true;
}
}
};
</script>
other component to call method from:
Open
Using a store is a a good solution for your need:
1/ create a store file store/index.js
export const state = () => ({
modal: false
});
export const mutations = {
SET_MODAL (state, value) {
state.modal = value
}
};
2/ update your layout to set store data to the modal component:
<template>
<div class="layout-wrapper">
<Nuxt />
<Modal open="$store.state.modal" />
</div>
</template>
3/ update your component to read the open value:
<template>
<div>
<el-dialog :visible.sync="open">
<div class="dialog-content">
<p>
Modal Content
</p>
</div>
</el-dialog>
</div>
</template>
<script>
import { Dialog } from "element-ui";
export default {
props: {
open: Boolean // see https://v2.vuejs.org/v2/guide/components-props.html
},
name: "Modal",
components: {
"el-dialog": Dialog
}
};
</script>
4/ toggle the store value from a nuxt page (see https://nuxtjs.org/docs/2.x/directory-structure/store)
<script>
export default {
methods: {
openModal (e) {
this.$store.commit('SET_MODAL', true);
}
},
...
}
</script>

Access this.$slots while using v-slot (scoped-slot)

In a specific use-case, I have to access the DOM element inside a slot to get its rendered width and height. With normal slots, this works by accessing this.$slots.default[0].elm.
Now I added a scoped-slot to access data inside the component, which led to this.$slots being empty and breaking my code.
How is it possible to access the DOM element of a slot that is using a slot-scope?
Basic example code (notice while using a scoped-slot, this.$slots results in {}; while using a normal slot, it results in {default: Array(1)}):
App.vue:
<template>
<div id="app">
<HelloWorld v-slot="{ someBool }">
<p>{{ someBool }}</p>
<h1>Hello</h1>
</HelloWorld>
<HelloWorld>
<h1>Hello</h1>
</HelloWorld>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue';
export default {
name: 'App',
components: {
HelloWorld,
},
};
</script>
HelloWorld.vue:
<template>
<div class="hello">
<slot :someBool="someBool" />
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
someBool: false,
};
},
mounted() {
console.log(this.$slots);
},
};
</script>
Use $scopedSlots, which includes both scoped slots and non-scoped slots:
export default {
mounted() {
console.log(this.$scopedSlots.default())
}
}
demo
Maybe not directly related but wanted to share how to access slot values.
resources/js/components/product/Filter.vue
<template>
<span class="ml-5">
The Slot Values: <span class="text-yellow-400">{{filterValues}}</span>
</span>
</template>
<script>
export default {
data() {
return {
filterValues: ''
}
},
mounted() {
this.filterValues = this.$slots.default()[0].children
}
}
</script>
resources/views/products.blade.php
...
<product-filter-vue>Instrument, FX</product-filter-vue>
...

Vue: how to use component as prop doesn't display empty component no html tag of component

I have three components:
HugeBox.vue
Box.vue
Dog.vue
HugeBox contains a Box which in turn contains a Dog:
HugeBox -> Box -> Dog
I try to pass the Dog to the Box as a prop, however it doesn't work: all that gets displayed when I open HugeBox is
Box with
while it should be
Box with Dog
HugeBox.vue:
<template>
<Box :myComponent = "Dog"/>
</template>
<script>
import Dog from '../test/Dog.vue';
import Box from '../test/Box.vue';
export default {
components: {Dog, Box}
}
</script>
Box.vue:
<template>
<div>
<p>Box with</p>
<component :is = "myComponent"/>
</div>
</template>
<script>
export default {
props: {
myComponent: {
type: [String, Object],
}
}
}
</script>
Dog.vue:
<template>
<p>Dog</p>
</template>
What am I doing wrong?
In Vue, if you need to pass markup or other components into a child component you can use slots.
Slots allow you to nest components within other components, just as you do with HTML.
HugeBox.vue:
<template>
<Box>
<Dog/>
</Box>
</template>
<script>
import Dog from '../test/Dog.vue';
import Box from '../test/Box.vue';
export default {
components: {Dog, Box}
}
</script>
Box.vue:
<template>
<div>
<p>Box with</p>
<slot></slot>
</div>
</template>
<script>
import Box from '../test/Dog.vue';
export default {
props: {
myComponent: {
type: [String, Object],
}
},
components: { Dog }
}
</script>
The <slot> tag is used to specify where the nested content should be displayed.
EDIT:
So you can actually pass components as props, and display them using the <component :is="..." /> method.
The reason that it's not working is that your original HugeBox.vue component doesn't have access to the Dog component as a template variable. You have to assign it to a data property first:
<template>
<Box :myComponent="dog"/>
</template>
<script>
import Dog from "../test/Dog.vue";
import Box from "../test/Box.vue";
export default {
components: { Box },
data() {
return {
dog: Dog // assign the Dog component object to data, allowing it to be passed as a prop
};
}
};
</script>
You have to import your Dog component inside your Box component.
Not inside your HugeBox component.
HugeBox.vue
<template>
<Box :myComponent = "'Dog'"/>
</template>
<script>
import Dog from '../test/Box.vue';
export default {
components: {Box}
}
</script>
Box.vue
<template>
<div>
<p>Box with</p>
<component :is = "myComponent"/>
</div>
</template>
<script>
import Box from '../test/Dog.vue';
export default {
props: {
myComponent: {
type: [String, Object],
}
},
components: { Dog }
}
</script>
Dog.vue
<template>
<p>Dog</p>
</template>

How to Access Component HTML output in Vue outside the template tag?

I know that we can simply show the component output with <ComponentName/> inside the template,
but how do we access ComponentName html output outside the template like in data, methods, or during mounted
e.g. components/Test.vue
<template>
<div>I'm a test</div>
</template>
in another vue file pages/ViewTest.vue
import Test from '~/components/Test.vue'
export default {
components: {Test},
data() {
return {
test: Test
}
},
mounted: function() {
console.log( Test ) // Output is Test Component Object
console.log( this.test ) // Output is Test Component Object
}
}
The object from console log output seems to contain a lot of information and I can even see a render property from the object although when I try console.log( Test.render() ) its giving me error
So My question is how can I get the <div>I'm a test</div> from outside the template?
Appreciate any help or guidance
EDIT
I'm using vue-material-design-icons package for generating different svg icons,
and I can use it like below
<template>
<MapMarkerRadius/>
</template>
<script>
import MapMarkerRadius from 'vue-material-design-icons/MapMarkerRadius'
export default {
components: {MapMarkerRadius}
}
</script>
Now here's my main issue,
I have this component that generates an html
<template>
<div :class="'card'">
<div v-if="title" :class="'card-title'">
{{ title }}
</div>
<div :class="'card-content'">
<slot />
</div>
</div>
</template>
<script>
export default {
name: 'card',
props: {
title: {},
}
};
</script>
Then I'm using that card component like this on a different vue file
<template>
<card :title="'Title ' + MapMarkerRadius">
Test Content
</card>
</template>
<script>
import card from '~/components/Card'
import MapMarkerRadius from 'vue-material-design-icons/MapMarkerRadius'
export default {
components: {card, MapMarkerRadius}
};
</script>
and my problem here is that the output of the card title is Title [object]
Try to use ref in the root of the Test component like :
<template>
<div ref="test">I'm a test</div>
</template>
in other component do :
mounted: function() {
console.log( this.$refs.test )
}
No need to import the component.
The repo that you are using are single-file components that generates html through a single tag, so using
import MapMarkerRadius from 'vue-material-design-icons/MapMarkerRadius'
will enable you to use it in template as <map-marker-radius/>
That is why appending the string title and an object like "My Icon"+MapMarkerRadius will return the literal [object] as you've seen: "My Icon [object]"
You have 3 options to go through what you want:
Search for other repos that enable you to use easily material icons in other means;
You have access to the card component? You can use the class names of this repo instead rather than the svg version or the component itself: https://github.com/robcresswell/vue-material-design-icons/issues/12, add the class names to the props and add it to your component:
<card :title="'Title'" :icon_class="map-marker-radius">
Test Content
</card>
<div v-if="title" :class="'card-title'">
{{ title }} <div :class="icon_class"></div>
</div>
props: {
title: {},
icon_class: '',
}
You can use the MapMarkerRadius component directly in card component but only appears when you pass a certain criteria on the card component, such like:
main.vue
<template>
<card :title="'Title'" :icon="true" :icon_typename="'map-marker-radius'">
Test Content
</card>
</template>
<script>
import card from '~/components/Card'
export default {
components: {card}
};
</script>
with icon_typename as any name/keyword you'd like to use.
card.vue
<template>
<div :class="'card'">
<div v-if="title" :class="'card-title'">
{{ title }} <span v-if="icon_mmr"><map-marker-radius/></span>
</div>
<div :class="'card-content'">
<slot />
</div>
</div>
</template>
<script>
import MapMarkerRadius from 'vue-material-design-icons/MapMarkerRadius'
export default {
name: 'card',
props: {
title: {},
icon: { default: false },
icon_typename: '',
icon_mmr: false,
},
mounted(){
if (this.icon && this.icon_typename === 'map-marker-radius') this.icon_mmr = true
},
components: { MapMarkerRadius },
};
</script>
The code is far from perfect but you can go from there to optimize further.

Categories

Resources