Vue - detect component inside slot - vue.js

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
}
}

Related

Vue template vs Vue jsx

could you explain Vue template vs Vue function jsx, what is different of it ? which one is good for use ?
Ex :
I have two components :
Component1.vue
<template>
<div>
<p>{{message}}</p>
</div>
<template>
<script>
export default {
name:'Component1',
data(){
return{
message:'This is component1'
}
},
}
</script>
Component2.vue
export default {
name:'Component2',
data(){
return{
message:'This is component2'
}
},
render(){
return(<p>{this.message}</p>)
}
}
Could I write like component2.vue ? How about performance of both ?
Both versions of writing the component will do the same thing. As far as the performance is considered, there would be no difference. Both are compiled into render function that returns Virtual DOM tree for your component.
The difference is the flavor and syntax of the implementation. Though with Vue, we mostly use templates as they are more readable over JSX, there are situation where JSX is more appropriate to use. For example, consider the case where you are trying to design a dynamic header component where level prop decides which <h1...h6> tag to use:
<template>
<h1 v-if="level === 1">
<slot></slot>
</h1>
<h2 v-else-if="level === 2">
<slot></slot>
</h2>
<h3 v-else-if="level === 3">
<slot></slot>
</h3>
<h4 v-else-if="level === 4">
<slot></slot>
</h4>
<h5 v-else-if="level === 5">
<slot></slot>
</h5>
<h6 v-else-if="level === 6">
<slot></slot>
</h6>
</template>
Same thing can be written more elegantly using render function or JSX:
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // tag name
this.$slots.default // array of children
)
},
props: {
level: {
type: Number,
required: true
}
}
});
Also, if you are using TypeScript, JSX syntax will provide you compile-time check for validating props and attributes, though setting that with Vue 2 is quite an hassle. With Vue 3, that is much simpler.
As far as dynamic loading of component is considered, you can use built-in <component /> component with is prop within the template as:
<component v-bind:is="someComponentToRenderDynamically"></component>
So, this brings the same advantages as JSX or direct render function based component. For more documentations see:
Dynamic Components
Render Function & JSX
First of all let's see what are Template syntax and JSX:
JSX: A syntax extension for JavaScript that lets you write HTML-like markup inside a JavaScript file. Basically, JSX is a JavaScript render function that helps you insert your HTML right into your JavaScript code.
Template syntax: An HTML-based template syntax that allows you to declaratively bind the rendered DOM to the underlying component instance's data.
Using Vue templates is like using JSX in that they’re both created using JavaScript. The main difference is that Vue templates are syntactically valid HTML that can be parsed by spec-compliant browsers and HTML parsers.
What does it mean?
JSX functions are never used in the actual HTML file, while Vue templates are.
What is the difference? which one is better to use ?
According to the Vue.js documentation, Vue compiles the templates into highly-optimized JavaScript code.
But if you are familiar with Virtual DOM concepts and prefer the raw power of JavaScript, you can also directly write render functions instead of templates, with optional JSX support.
However, do note that they do not enjoy the same level of compile-time optimizations as templates.
So, we can conclude that writing template syntax with Vue is more optimized.
The vue template is much more readable and easier to understand than jsx functions.
It's much easier to save variables / properties and access them using "{{someVariables}}", rather than always telling a vue method to read them
In short, it's better to use the vue template because it's easier to implement dynamic pages with it (and other things).
Also, at this point it's not a very good idea to keep sending html code through methods.

How to pass cell templates to a component with b-table?

I created a component that shows table data for various pages. That component uses b-table inside. Now for a couple pages I want to customize rendering of some columns, and Bootstrap Tables allow that using scoped field slots with special syntax:
<template #cell(field)="data">
{{ data.item.value }}
</template>
where field - column name, coming from my array with columns, and data.item - cell item to be rendered.
The problem is that I have different fields for different pages, so this customization should come from parent component, and these templates should be created dynamically.
Here is how I tried to solve it:
Pass via property to MyTableComponent an array with customizable fields and unique slot names
In MyTableComponent dynamically create templates for customization, and inside dynamically create named slots
From parent pass slot data to named slots
MyTableComponent:
<b-table>
<template v-for="slot in myslots" v-bind="cellAttributes(slot)">
<slot :name="slot.name"></slot>
</template>
</b-table>
<script>
...
computed: {
cellAttributes(slot) {
return ['#cell(' + slot.field + ')="data"'];
}
}
...
</script>
Parent:
<MyTableComponent :myslots="myslots" :items="items" :fields="fields">
<template slot="customSlot1">
Hello1
</template>
<template slot="customSlot1">
Hello2
</template>
</MyTableComponent>
<script>
...
items: [...my items...],
fields: [...my columns...],
myslots: [
{ field: "field1", name: "customSlot1" },
{ field: "field2", name: "customSlot2" }
]
...
</script>
Unfortunately, b-table component just ignores my custom slots like if they are not provided. It works if I specify in the MyTableComponent it directly:
<b-table>
<template #cell(field1)="data">
{{ data.item.value }}
</template>
</b-table>
But I need it to be done dynamically via component properties. Please help.
You can use Dynamic Slot Names feature of Vue 2 to pass all (or some) slots from parent to <b-table> inside child like this:
Child:
<b-table>
<template v-for="(_, slotName) of $scopedSlots" v-slot:[slotName]="scope">
<slot :name="slotName" v-bind="scope"/>
</template>
</b-table>
$scopedSlots contains all slots passed to your component.
Now this will work:
<MyTableComponent :items="items" :fields="fields">
<template #cell(field1)="data">
{{ data.item.value }}
</template>
</ MyTableComponent>
UPDATE 2 - Vue 3
To make above code work in Vue 3, just replace $scopedSlots with $slots as suggested by migration guide
UPDATE 1
You can filter $scopedSlots if you want (have some slot specific to your wrapper component you don't want to pass down to <b-table>) by creating computed
I mentioned this possibility in my original answer but it is a bit problematic so it deserves better explanation...
Scoped slots are passed to a component as a functions (generating VNode's when called). Target component just executes those she knows about (by name) and ignores the rest. So lets say your wrapper has b-table (or v-data-table for Vuetify) inside and some other component, let's say pagination. You can use code above inside both of them, passing all slots to each. Unless there is some naming conflict (both components using same slot name), it will work just fine and does not induce any additional cost (all slot functions are already compiled/created when passed to your wrapper component). Target component will use (execute) only the slots it knows by name.
If there is possible naming conflict, it can be solved by using some naming convention like prefixing slot names intended just for b-table with something like table-- and doing filtering inside but be aware that $scopedSlots object does contain some Vue internal properties which must be copied along !! ($stable, $key and $hasNormal for Vue 2 - see the code). So the filtering code below even it's perfectly fine and doesn't throw any error will not work (b-table will not recognize and use the slots)
<b-table>
<template v-for="(_, slotName) of tableSlots" v-slot:[slotName]="scope">
<slot :name="slotName" v-bind="scope"/>
</template>
</b-table>
computed: {
tableSlots() {
const prefix = "table--";
const raw = this.$scopedSlots;
const filtered = Object.keys(raw)
.filter((key) => key.startsWith(prefix))
.reduce(
(obj, key) => ({
...obj,
[key.replace(prefix, "")]: raw[key],
}),
{}
);
return filtered;
},
},
This code can be fixed by including the properties mentioned above but this just too much dependency on Vue internal implementation for my taste and I do not recommend it. If it's possible, stick with the scenario 1...

When to use prop modifier in vue?

When to use prop modifier in vue?
In vue document,I find the prop modifier in v-bind here: https://v2.vuejs.org/v2/api/#v-bind
my question is this:
https://v2.vuejs.org/v2/api/#v-bind
From the docs:
Modifiers:
.prop - Bind as a DOM property instead of an attribute (what’s the difference?). If the tag is a component then .prop will set the property on the component’s $el.
This is useful, because some HTML inputs can't receive a value as attribute, but need to be passed on the ref itself.
See for example the indeterminate state of a checkbox. Normally you'd need to do
<template>
<!-- this doesn't work, because there's no indeterminate prop on the checkbox, it only exists on the DOM element -->
<input type="checkbox" :indeterminate="indeterminateProp" />
</template>
<script>
beforeUpdate() {
//set indeterminate directly on the dom element object
// this is what you'd always have to do if v-bind.prop didn't exist
this.$refs.myCheckbox.indeterminate = this.indeterminateProp;
}
</script>
and keep track of it manually outside of vue's reactivity system.
But thanks to .prop, you can just do
<input type="checkbox" :indeterminate.prop="indeterminateProp"/>
Without having to play with lifecycle hooks.
So .prop basically tells vue "apply this value on the DOM node itself (the one you'd normally get from $refs) instead of trying to apply it as an attribute or property"
You can use this modifier to pass a property to a component.
<myComponent v-bind:<PropName></myComponend> is the same as <myComponent :<PropName>></myComponend>
Checkout this link here: https://v2.vuejs.org/v2/guide/components-props.html.

Vue, change native event not working, input native does

I have a question and maybe a Vue bugg.
I have a custom component that needs a #change.native event. But it does not trigger anything and I could not find anything about this issue myself.
So i tried some different stuff and like #click.native and #input.native does work. Even tho #input.native works and do the trick i want to, i still want to know why the change event does not work.
Anybody? Else I should report this.
Vue version: 2.5.2
<custom-input type="search"
placeholder="search"
v-model="search"
#input.native="change" />
If the <input /> inside the custom component is a child element of another element, then the event listener bound by the .native modifier will not be reached, since it is listening to the event of a different element.
custom-input.vue:
<template>
<div>
<input :value="someValue" />
</div>
</template>
<script>
export default {
props: ['value']
}
</script>
so if you have this scenario, then the #change.native will be bound on the <div> (the wrapper).
(sadly) You need to manually propagate the event manually.

Vue mounted jquery code not working on DOM re-render

I'm trying to use jqueryui datapicker with vue. The input for the datepicker is inside a template if-else condition.
The datepicker works just fine initially but after flipping back and forth using the template if-else it stops working.
It seems the jquery code is no longer mounted after a DOM re-render.
<template v-if="...">
<input name="startdate" id="startdate" v-model="date" type="text">
</template>
<template v-else>
<div>Some other display</div>
</template>
mounted: function() {
$('#startdate').datepicker({
onSelect:function(selectedDate, datePicker) {
this.date = selectedDate;
}
});
I placed the jquery code instead inside "updated" and it works now, but this code gets called every single time there is a DOM change.
I'm wondering it there is a better way to accomplish this?
updated: function () {
this.$nextTick(function () {
....
....
You might use v-show instead. This will only toggle the display value of the element, not remove from or add to the DOM, so any event handlers attached won't be affected. From the Vue docs: https://v2.vuejs.org/v2/guide/conditional.html#v-show
That said, #Imre_G is probably right about finding a Vue datepicker to use.
Try this: use your component with mouted() hook and just add to your component the v-once directive. Like this: <input name="start date" ... v-once>
But, as mentioned in Imre_G comment, consider to use some premaded Vue datepicker component. It is far more better idea as mixing Vue with jQuery.