I am a rather new Vue developer.
Everywhere I go in our stack, I see code like this inside our components components:
<template #item.active="{ value }">
<div :aria-label="String(value)" class="text-center">
<v-icon v-if="value === null">mdi-minus</v-icon>
<v-icon v-else color="red">mdi-close</v-icon>
</div>
</template>
And for the life of me, I am cannot figure out what the #item.active (specifically the #) actually does. We have many hashed items. Like <template #item.actions-prepend="{item}"> or <template #toolbar-extension>
Googling a # isn't an easy thing to do.
And apparently I missed this specific video in my Vue tutorials!
We use Nuxt and Vuetify, not sure if that helps!
As mentioned in the comments, the # symbol is a shorthand for the v-slot attribute, as hinted by the usage of <template> (which v-slot only allows to be used on, as well as components) in your code.
Related
I am working with Vuejs. I want to render components based on value of variable val.
My component looks like this
<template v-if="this.$val===1">
<component1 />
</template>
<template v-if="this.$val===2">
<component2 />
</template>
I have defined a global variable val using Vue.prototype and I am updating it using onclick function,where I am changing value of val to 2 but after clicking it doesnt show component2 instead of component 1.
Define val globally in main.js using following line of code
Vue.prototype.$val = 1;
Can someone please help me with this. Thanks
td,dr; Vue.prototypeis not reactive.
I'm going to enumerate issues as I observe them, hoping you'll find them useful.
You're not specifying which version of Vue you're using. Since you're using Vue.prototype, I'm going to guess you're using Vue 2.
Never use this in a <template>.
Inside templates, this is implicit (sometimes formulated: "inside templates this doesn't exist"). What would be this.stuff in controller, is stuff in the template.
You can't conditionally swap the top level <template> of a Vue component. You need to take the conditional either one level up or one level down:
one level up would be: you create separate components, one for each template; declare them and have the v-if in their parent component, rendering one, or the other
one level down would be: you move the v-if inside the top level <template> tag of the component. Example:
<template><!-- top level can't have `v-if` -->
<div v-if="val === 1">
val is 1
<input v-model="val">
</div>
<div v-else>
val is not 1
<input v-model="val">
</div>
</template>
<script>
export default {
data: () => ({ val: 1 })
}
</script>
Note <template> tags don't render an actual tag. They're just virtual containers which help you logically organise/group their contents, but what gets rendered is only their contents.1 So I could have written the above as:
<template><!-- top level can't have v-if -->
<template v-if="val === 1">
<div>
val is 1
<input v-model="val">
</div>
</template>
<template v-else>
<template>
<template>
<div>
val is not 1
<input v-model="val">
</div>
</template>
</template>
</template>
</template>
And get the exact same DOM output.
For obvious reasons, <template> tags become useful when you're working with HTML structures needing to meet particular parent/child constraints (e.g: ul + li, tr + td, tbody + tr, etc...).
They're also useful when combining v-if with v-for, since you can't place both on a single element (Vue needs to know which structural directive has priority, since applying them in different order could produce different results).
Working example with what you're trying to achieve:
Vue.prototype.$state = Vue.observable({ foo: true })
Vue.component('component_1', {
template: `
<div>
This is <code>component_1</code>.
<pre v-text="$state"/>
<button #click="$state.foo = false">Switch</button>
</div>
`})
Vue.component('component_2', {
template: `
<div>
This is <code>component_2</code>.
<pre v-text="$state"/>
<button #click="$state.foo = true">Switch back</button>
</div>
`})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue#2.7.10/dist/vue.min.js"></script>
<div id="app">
<component_1 v-if="$state.foo"></component_1>
<component_2 v-else></component_2>
</div>
Notes:
<div id="app">...</div> acts as <template> for the app instance (which is, also, a Vue component)
Technically, I could have written that template as:
<div id="app">
<template v-if="$state.foo">
<component_1 />
</template>
<template v-else>
<component_2 />
</template>
</div>
, which is pretty close to what you were trying. But it would be slightly more verbose than what I used, without any benefit.
I'm using a Vue.observable()2 for $state because you can't re-assign a Vue global. I mean, you can, but the change will only affect Vue instances created after the change, not the ones already created (including current one). In other words, Vue.prototype is not reactive. This, most likely, answers your question.
To get past the problem, I placed a reactive object on Vue.prototype, which can be updated without being replaced: $state.
1 - there might be an exception to this rule: when you place text nodes inside a <template>, a <div> wrapper might be created to hold the text node(s). This behaviour might not be consistent across Vue versions.
2 - Vue.Observable() was added in 2.6.0. It's a stand-alone export of Vue's reactivity module (like a component's data(), but without the component). In v3.x Vue.Observable() was renamed Vue.reactive(), to avoid confusion/conflation with rxjs's Observable.
global variables are accessed in template without this keyword which means $val===1 will work.
Solution 1:
<template>
<component1 v-if='$val === 1' />
<component2 v-else/>
</template>
This will work.
But you could make use of dynamic components in your case.
Solution 2:
<template>
<component :is='currentComponent'/>
</template>
<script>
\\imports of your components will go here
export default {
name: 'app',
components: {
component1, component2
},
computed:{
currentComponent(){
return this.$val === 1?component1:component2;
}
}
}
</script>
Dynamic components are more performant and helps you maintain state of component.
I have a custom library built on Stencil that I need to integrate with Vue, and this library relies on the browser's <template /> tag.
For example, I need to render the following code to the dom:
<my-custom-list>
<template>
<my-custom-title></my-custom-title>
<my-custom-description></my-custom-description>
</template>
</my-custom-list>
my-custom-list will then get whatever is inside the template and use it for each of the items in the list.
I managed to get it to work by using the v-html directive, for example:
<my-custom-list v-html="<template><my-custom-title></my-custom-title><my-custom-description></my-custom-description></template>">
</my-custom-list>
However, I would also like to be able to use Vue components inside this template tag, like:
<my-custom-list>
<template>
<my-custom-title></my-custom-title>
<my-custom-description></my-custom-description>
<custom-vue-component></custom-vue-component>
</template>
</my-custom-list>
Some old answers online suggest using the is directive to be able to output the template tag, like <div is="template"> but that has been deprecated.
I am not even sure if this is possible, but any insights would be much appreciated.
You can use the v-pre directive to skip compilation for parts of your components.
<my-custom-list v-pre>
<template>
<my-custom-title></my-custom-title>
<my-custom-description></my-custom-description>
<custom-vue-component></custom-vue-component>
</template>
</my-custom-list>
I'm learning vue right now and have problems with understanding the slots.
I got two components:
BaseIcon
<template>
...
<slot name="test"></slot>
...
<template/>
EventCard
<template>
<router-link class="event-link" :to="{ name: 'event-show', params: {id: '1'}}">
..
<BaseIcon name="users" slot="test">{{ event.attendees.length }} attending</BaseIcon>
..
</router-link>
</template>
But the the "slot" ain't replaced with the content in the BaseIcon component tags in EventCard.
You could use it like v-slot because slot syntax is deprecated as mentioned here:
<BaseIcon name="users">
<template v-slot:test>
{{ event.attendees.length }} attending
</template>
</BaseIcon>
or a shorthand :
<BaseIcon name="users" >
<template #test>
{{ event.attendees.length }} attending
</template>
</BaseIcon>
I had a very similar problem recently. Turned out there was a tiny glitch in my HTML markup (I hadn't closed a bold tag). Presumably, the Vue routine that converts template HTML into Javascript script is super sensitive to this kind of glitch (unlike browsers which have been brilliant at coping with them for years) - so the template was silently failing.
I had to use a laborious "divide and conquer" process to track it down - chop everything out, then paste it back bit by bit. But there may be syntax checkers out there that would analyse your template markup thoroughly.
Worth checking.
The named v-slot can be used only in the template. Default can be used in the component too. See the docs: https://v2.vuejs.org/v2/guide/components-slots.html#Abbreviated-Syntax-for-Lone-Default-Slots
Also:
Note that v-slot can only be added to a (with one exception), unlike the deprecated slot attribute.
https://v2.vuejs.org/v2/guide/components-slots.html#Named-Slots
Good afternoon, I modified the group header slot to customize the group row, only I would like to set the value isOpen = false by default and I can't find a way to do it, I would appreciate your help.
<template v-if="group_by" v-slot:group.header={group,items,headers,isOpen,toggle}>
<td v-for="header in headers" #click="toggle(items[0].category)">
<template v-if="header.group_header">
<template v-if="link_row">
<strong><a :href=setInvoiceLink(group)>{{group}}</a> ({{getQuantity(group)}})</strong>
</template>
<template v-else>
<strong>{{group}} ({{getQuantity(group)}})</strong>
</template>
<strong style="color:blue" v-if="group_extra_title"> - {{getExtraTitle(group,group_extra_title)}}</strong>
</template>
<template v-if="header.sum">
<strong>{{MoneyFormat(getSuma(header.value,group))}}</strong>
</template>
<template v-if="header.value == 'data-table-select'">
<v-checkbox
:disabled="enable_if"
:input-value="check_checkbox(group)"
#change="selectAllInvoiceAction(group,$event)"
></v-checkbox>
</template>
</td>
</template>
I thought the same thing than you, that I could change the default behavior from group-by prop from v-data-table.
Looking deeper in the GitHub code I saw the Push Request that added the isOpen prop to group-header slot and an example of its usage. Here it is:
<template>
<v-container>
<v-data-table :items="items" :headers="headers" group-by="type">
<template #group.header="{ isOpen, toggle }">
<v-btn #click="toggle" icon>
<v-icon>
{{ isOpen ? '$minus' : '$plus' }}
</v-icon>
</v-btn>
</template>
</v-data-table>
</v-container>
</template>
As you can see it is just a reactive prop to inform the slot if the group header is open or closed. If you want to add a button to open or close all at the same time, this another stackoverflow question show you how:
Collapse or expand Groups in vuetify 2 data-table
The logical place to inform that you want all the groups originally closed would be at the v-data-table props, but it isn't implemented yet as you can see at the props from the source code.
v-data-table source code
****EDIT****
After thinking about how to solve this issue I came to this solution that will work on your build code.
On your chunk-vendors.[hash].jsfile from your dist/js folder remove the ! from this code before the 0 (zero) at the end.
genGroupedRows:function(t,e){var n=this;return t.map((function(t){return n.openCache.hasOwnProperty(t.name)||n.$set(n.openCache,t.name,!0)
What will make look like this:
genGroupedRows:function(t,e){var n=this;return t.map((function(t){return n.openCache.hasOwnProperty(t.name)||n.$set(n.openCache,t.name,0)
The chunk file is hard to read because of the uglify process. But you just need to find the genGroupedRows function in the middle of it, and remove the exclamation point. In other words, you are just saying to the source code from Vuetify to create groups closed by default.
You can make it work on your dev too, but in this case you would need to change the source code from vuetify module. Same function name genGroupedRows.
Since I also encountered the problem, I share my solution, which can be done within the component:
Working example: https://codepen.io/joke1/pen/bGEPdYL
...
mounted () {
let table = this.$refs.table;
let keys = Object.keys(table.$vnode.componentInstance.openCache);
keys.forEach(x => {
table.$vnode.componentInstance.openCache[x] = false;
})
}
...
<v-data-table ref="table" ...></v-data-table>
The solution provided by #jk1 worked perfectly for me.
Additionally, my requirement was, to keep the first group "open" and so I could easily achieve this by removing (popping) the last entry from the keys array.
let table = this.$refs.table;
let keys = Object.keys(table.$vnode.componentInstance.openCache);
keys.pop() //remove last element so that first group stays open
keys.forEach(x => {
table.$vnode.componentInstance.openCache[x] = false;
})
It worked like a charm.
Can I not do something like this?
<template v-if="options.columns">
one thing...
</template>
<template v-else slot-scope="scope">
something else. scope doesn't work here. :(
</template>
When I try to do this, scope becomes undefined. The moment I remove v-if, else and use scope it works as expected.
I was facing with the exact same problem, what's worse, mine is failing silently - I didn't see any errors.
Solution #1 move if else condition to the inner scope of <template>
As David Japan pointed out, you can check if else condition in the inner scope of slot-scope:
<template slot-scope="scope">
<span v-if="options.columns">one thing...</span>
<span v-else>something else.</span>
</template>
Solution #2 use v-slot instead of slot-scope
<template v-slot="scope" v-if="options.columns">
one thing...
</template>
<template v-slot="scope" v-else>
something else.
</template>
However I don't know why v-slot fixes it, I searched both in the official documentation and online but no clues.
The solution is a workaround:
Create 2 different SFC for each possible use case and do conditional rendering.
Vuetify example:
// App.vue
<div>
<ListMD v-if="this.$vuetify.breakpoint.name === 'md'" />
<ListXS v-if="this.$vuetify.breakpoint.name === 'xs'" />
</div>
In Vue.js component you cannot have more than one template tag.
Why not using 'is' property for dynamic component ?
<template>
<div :is='my-component'></div>
</template>
So you can load a component dynamicaly.
https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components