how to create slot to wrap content - vue.js

I'm looking for a way to create a slot that allows me to wrap its content with arbitrary markup, instead of replacing the content...
Consider a Template like this (obviously doesn't work):
<!-- this slot ... -->
<slot name="wrapper">
<SomeComplexComponent ... />
</slot>
<!-- becomes -->
<div class="wrapper" style="background: red;">
<SomeComplexComponent ... />
</div>
This is for example needed for Table components, wrapping the table header in a sticky component, to allow scrolling while keeping the header visible (see the DetailsList Fabric example: https://codepen.io/johannes-z/pen/QWbZvqx?editable=true).
In React, and Fabric, they circumvent this problem by using render functions, and passing the original render function to the slotable render function. This way they can wrap the original render function with arbitrary markup/components.
Is there a way to achieve this with Vue? Preferably using template syntax and not render functions / tsx.

Related

vue passing props to sub component will generate html attributes to wrapper root

So I have this component PageWrapper that has a title props.
<div id="page-wrapper">
<h1>{{title}}</h1>
<slot />
</div>
And I have a wrapper component that forwards most props to its child:
<PageWrapper v-bind="[$attrs]">
<slot name="default"/>
</PageWrapper>
It works as expected, except that my page wrapper root has an extra title html attribute that I don't want to be there.
<div id="page-wrapper" title="My Title">
<h1>My title</h1>
<!-- rest of the page -->
</div>
I have the feeling there is a confusion between html attributes and vue properties when forwarding to sub-component.
I thought this had to do with the difference between $props and $attrs but apparently not as it doesn't work in any case.
Can someone clarify this for me? How can I avoid this html-attributes behavior?
Cheers
you do not want to bind the attributes to the PageWrapper Component.
Instead just bind the title property.
<PageWrapper :title="title">
<slot name="default"/>
</PageWrapper>
EDIT:
Might also try to bind the attributes without the square brackets v-bind="$attrs"

Vue3: Using v-for with keep-alive

My Problem
I have a variable number of Tab components; there can be 1 or 10, but only one is going to be displayed at a time. However, I want to cache them between switches. I thought I could put a v-for loop inside a <keep-alive> but according to the docs: https://v3.vuejs.org/api/built-in-components.html#keep-alive:
Note, <keep-alive> is designed for the case where it has one direct child component that is being toggled. It does not work if you have v-for inside it. When there are multiple conditional children, as above, <keep-alive> requires that only one child is rendered at a time.
Question
This seems like a pretty standard use-case, so I'm wondering if there's an easier way about this that I'm missing. Is there a best practice to rendering a variable number of components inside the <keep-alive> component?
Update: Kind of an answer
I was able to separate the Tab logic from the v-for, which means the Tabs get generated elsewhere, while the TabContent is the only thing inside <keep-alive>. Kind of like so:
<keep-alive>
<TabContent tab-name="name" />
</keep-alive>
Then internally, TabContent uses the tab-name prop to grab the data it needs.
Admittedly it feels like a good solution but I'm open to other ways to solve this.
Update 2.0: For Dynamic Components, use :key
The last thing I figured out; my Tab titles are dependent on filenames, and the TabContent is generated from the file, so it takes a filename props. My content was dynamic.
You still can't use a v-for inside the <keep-alive>, but you can use keys still:
<keep-alive>
<template>
<component
:is="'TabContent'"
:key="currentTabTitle"
v-bind="{
filename: currentTabTitle
}"
/>
</template>
</keep-alive>
Take a look at Dynamic Components with keep-alive
<div id="dynamic-component-demo" class="demo">
<button
v-for="tab in tabs"
:key="tab"
:class="['tab-button', { active: currentTab === tab }]"
#click="currentTab = tab"
>
{{ tab }}
</button>
<!-- Inactive components will be cached! -->
<keep-alive>
<component :is="currentTabComponent"> </component>
</keep-alive>
</div>

Vue - detect component inside slot

I have this slot inside my tile component. I basically need to detect a specific other component which is supposed to be used inside this slot BUT the slot also supports other html tags not just this specific component. Is there a way to detect a special component e.g. <listitem /> inside the slot?
<div class="tile">
<template v-if="$slots['headline']">
<slot name="headline" />
</template>
</div>
Edit
The basic idea is the following
<tile>
<template #headline>
<listitem />
</template>
</tile>
<tile>
<template #headline>
<h1>Some headline</h1>
</template>
</tile>
I have those two options on how you can utelise this header slot. If there is a just a normal html tag e.g. <h1>, I would like to apply the corresponding css styles. If there is the <listitem /> component I need to apply other styles
As content of the slot is passed to the component as an array of VNode's accessible via this.$slots (in case of scoped-slots it is function returning array of VNode's) you can write a function like this:
methods: {
isListitem() {
return this.$slots.headline && this.$slots.headline[0].tag.endsWith('-listitem')
}
}
Main problem is that $slots is not reactive
Docs:
Please note that slots are not reactive. If you need a component to re-render based on changes to data passed to a slot, we suggest considering a different strategy that relies on a reactive instance option, such as props or data
So I don't recommend doing this and follow the Vue documentation suggestion to use props instead...
Maybe you can use a function to try to get this element by js, something like this:
I am not sure if its will works
function checkElement(){
var listitem = document.querySelector("listitem");
if(listitem) {
// if exist do something
}
}

is special attribute vs v-if / v-show

is special attribute is:
<!-- Component changes when currentTabComponent changes -->
<component v-bind:is="currentTabComponent"></component>
conditional rendering is:
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
I know the different between using v-if and v-show, but I don't know the difference between using a list of v-ifs for different cases vs using the is special attribute. When should I use them?
Does is work like v-if or v-show? I mean, does it render all the components anyways? Is is like a syntactic sugar for a list of subsequent v-ifs?
is would be useful if the list of v-ifs would all render a component if true.
Like so:
<template v-if="component == "firstComponent">
<first-component></first-component>
<template v-else-if="component == "secondComponent">
<second-component></second-component>
</template>
<template v-else-if="component == "thirdComponent">
<third-component></third-component>
</template>
This can then be reduced to:
<component :is="component"></component>
Concerning your second question
Wether component :is works like v-if or v-show depends on wether you wrap it in <keep-alive> or not. Read the documentation on this here. Note though, that using <component> a component only gets created until it is necessary the first time, wether you use <keep-alive> or not.
So:
v-if (re)creates the component everytime the condition is met.
<component :is="..."> creates the component everytime the condition is met (like v-if).
<keep-alive><component :is="..."></keep-alive> creates the
component at most 1 time (but possibly 0).
A component with only v-show on it is created exactly once.

Vue: What is the cleanest way to select a component via CSS?

I have a bar component. It is used like this:
<template>
<div>
<!-- stuff -->
<bar></bar>
<!-- stuff -->
<!-- stuff -->
<!-- stuff -->
<bar></bar>
<!-- stuff -->
<bar></bar>
<!-- stuff -->
</div>
</template>
<style lang="scss" scoped>
#media (max-width: 1300px) {
// this selector doesn't work, but it would be nice if it did
bar {
display: none;
}
}
</style>
I would like to hide the bar elements when the screen is 1300px or narrower. It would be nice if there was a bar element selector, just like there are p and h1 element selectors. However, there doesn't seem to be, and I have to add class="bar" in order to select them.
My question is if there is a cleaner way to select the bar elements.
It wouldn't be good to add the CSS code inside of the bar component because when the bars are used inside of other components, I don't want to hide them at all.
A class seems to be the best way to go. Put it on the root element of the component if you want it to be universal for the component, or only on the component tag if you want it to be specific to that use.
Also, there is no reason you couldn't use a custom tag as the root element of your component; as long as the tag didn't map to a component, it would be left in the DOM, and you could use it for CSS selection. I don't recommend it, though, as I don't think this use case is a good reason for introducing a new tag.
If your component template looked like this, for example:
<template>
<bar-container>
Hi there
</bar-container>
</template>
and you had no bar-container component defined, you would be able to use CSS to select bar-container, which would be the container element for every bar component. But it's just as easy to use <div class="bar-container"> instead.
After better understanding the problem at hand, would this work?
<div class="someClass">
<bar v-bind:width="draw.probability" type="draw" ref="myComponent"></bar>
</div>