Vue and Bootstrap Vue - dynamically use slots - vue.js

I'm trying to make in a bootstrap-vue table a slot to render any boolean value with a custom component.
So I have a simple table
<b-table :items="items" :fields="columns" >
</b-table>
Now if i want to render a single column in a particular way i have to use a slot
<template v-slot:cell(active)="data" >
<my-component :item="data.item" />
</template>
And it works, because I know that active is a boolean.
I would like to generalize this behavior but i cannot use v-for in templates and cannot use v-slot:cell(active) if not on template... The idea was to create an array with all my boolean fields and iterate on it... but it does not work..
Something like this
<template v-slot:cell(b)="data" v-for="b in booleanFields">
<my-component :item="data.item[b]" />
</template>

Because Vue supports Dynamic Slot Names, you can use variables to set the slot names using the v-bind:[attributeName]="value" syntax.
This way you could do something like:
<template v-slot:['cell(' + b + ')']="data" v-for="b in booleanFields">
But using the quotes there is not possible due to the dynamic argument expression constraints. So you'll have to create a helper method to do that concatenation. So:
<template v-slot:[gomycell(b)]="data" v-for="b in booleanFields">
plus
methods: {
gomycell(key) {
return `cell(${key})`; // simple string interpolation
}
Naturally, you could just name the method gomycell as cell and use it like v-slot:[cell(b)]="data" (notice the []s), but I left the name gomycell just so in this texample it is clearer what is the name of the method and what is not.
Demo:
Here's a small demo showcasing the dynamic slot names usage, it's not b-table but I think it is good enough to show it is possible:
Vue.component('my-table', {
template: '#my-table',
})
new Vue({
el: '#app',
data: {
booleanFields: [true, false]
},
methods: {
gomycell(key) {
return `cell(${key})`;
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<my-table>
<template v-slot:[gomycell(b)]="data" v-for="b in booleanFields">
<h3>who? {{ data.is }}</h3>
</template>
</my-table>
</div>
<template id="my-table">
<div>
<div style="color:green"><slot name="cell(true)" v-bind="{is: 'true!'}"></slot></div>
<div style="color:red"><slot name="cell(false)" v-bind="{is: 'false!'}"></slot></div>
</div>
</template>

Related

Add multiple components to child component via slot

I have a pretty simple implementation so I feel like this may be a silly question. Just starting with vue I am trying to make a component be able to list other components; kind of like a reusable List, Table, Grid, ect.
Parent component imports gridView as a normal component:
<template>
<div :id="id" class="grid-view">
<slot></slot>
</div>
</template>
And then in another component I attempt to build this:
<grid-view :dataSource="items">
<grid-view-item-test
v-for="(item, index) in items"
:key="index"
:title="item.title"
/>
</grid-view>
And then grid-view-item-test is simple. title being a prop. Works completely fine standing alone or without the use of a slot, but I wanted to make grid-view reusable to just take whatever other component I add and display as-is.
<template>
<div>
<div>{{ title }}</div>
</div>
</template>
If I were to do something like this:
<grid-view :dataSource="items">
<grid-view-item-test
:key="index"
:title="'Test 1'"
/>
<grid-view-item-test
:title="'Test 2'"
/>
<grid-view-item-test
:title="'Test 3'"
/>
</grid-view>
It works completely fine. Am I doing the loop wrong? I can confirm the loop works to get the number of items correctly. If I make a default fallback on the slot - I get the correct number, and I have printed it directly outside of the grid-view component.
Is this not a possibility? If not, would you just use HTML instead of a component for a reusable table as that seems to work fine.
Edit:
It works completely fine if I use an of strings, or numbers, but not objects.
I've tracked it down to items being an empty array as a computed variable and then throwing TypeError: null is not an object (evaluating 'oldChildren[i]'). Can confirm that items begins empty, and then is populated once a database call sets it, but I'm guess I'm not able to do something like that with slots?
After more testing it fails when you update the dataSet (items) at all. Is re-rending not possible with slots like this?
It works flawlessly if I have an empty div (or anything) when there are no items in the initial array.
You probably should provide more a more accurate code because I don't see any problem with what you provided
Working example:
const gridView = Vue.component('gridView', {
props: ['dataSource'],
template: `
<div class="grid-view">
<h4>My grid</h4>
<slot></slot>
</div>
`
})
const gridItem = Vue.component('grid-view-item-test', {
props: ['title'],
template: `
<div>
<div>{{ title }}</div>
</div>
`
})
const vm = new Vue({
el: '#app',
data: () => ({
items: [{
title: 'Test 1'
},
{
title: 'Test 2'
},
{
title: 'Test 3'
},
]
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<grid-view :data-source="items">
<grid-view-item-test v-for="(item, index) in items" :key="index" :title="item.title" />
</grid-view>
</div>

Vue v-if v-if-else time complexity

I'm doing a project vue and I wanted to know if vue v-if v-else-if time complexity is O(n) or O(1)
I'm using a component in vue like a switch case (takes input and returns html) something like
<div v-if="type === 'A'">
<!-- HTML Data -->
</div>
<div v-else-if="type === 'B'">
<!-- HTML Data -->
</div>
<div v-else-if="type === 'C'">
<!-- HTML Data -->
</div>
<div v-else>
<!-- HTML Data -->
</div>
but with a lot more cases.
Is that the recommended way to do something like this? or should I make a lot of small component and load them dynamically? which is O(1)
And the reason I have this component is that it takes in a string input and returns a custom icon for a data that can be changed, so they have to be loaded dynamically
I think it's better to create a type/value map and a computed property:
new Vue({
el:"#app",
data(){
return{
type: 'A',
types: {A:'A',B:'B',C:'C'}
}
},
computed:{
typeVal(){
return this.types[this.type];
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
{{typeVal}}
</div>

How to use "v-for", for array in computed object

I am creating a website to list all of my projects. I am using Vuex and Firebase to store all my data.
I want each project to have its own "todo-list".
When I create a new project, I create an array witch is my todo-list. In Firebase it looks like this: Screenshot of Firebase
Now I want to display a list of my projects. First I get my projects as a computed prop like this:
<script>
export default {
computed: {
projects () {
return this.$store.getters.loadedProjects
}
},
created () {
this.$store.dispatch('loadProjects')
},
}
</script>
I can loop through projects and display the title for each project.
My question is: how do I loop through the todo-array inside the computed prop?
<template>
<div v-for="project in projects" :key="project.id">
<p>{{ project.title }}</p>
<!-- Looping through todos (array) in an computed prop -->
<div v-for="(todo, index) in todos" :key="todo.id">
<p>{{ project.todos[index].title }} </p>
<p>{{ project.todos[index].completed }} </p>
</div>
</div>
</template>
Tried to use the computed prop inside v-for, but that does not work.
Not sure if I understand the meaning of computed properties. Could I just define projects () inside data () ? and link the getter to the data?
<div v-for="todo in {{project.todos}} :key="todo.id>
If the second loop is inside the first one, you could access it this way:
<template>
<div v-for="project in projects" :key="project.id">
<p>{{ project.title }}</p>
<!-- Looping through todos (array) in an computed prop -->
<div v-for="todo in project.todos" :key="todo.id">
<p>{{ todo.title }} </p>
<p>{{ todo.completed }} </p>
</div>
</div>
</template>
When you create a loop, in this case, project is a single element of the array, so project.todos is the list of todos of that specific project.
https://v2.vuejs.org/v2/guide/list.html#Mapping-an-Array-to-Elements-with-v-for

Output html using a Vue template tag, but without having a nested element

My over-arching question is: In Vue, how to output inner html while iterating over a collection but without having to wrap it in an additional div or span.
Specifically: I'm iterating over an array of js objects from within a Vue template. Each object has a "render" method which outputs html.
This works:
template:
`<div id="container">
<template v-for="element in elements">
<span v-html="element.render()"></span>
</template>
</div>
`,
But I would like to do something like below, because I don't want the output to be wrapped in a div or span. This code produces no error but also produces no output:
template:
`<div id="container">
<template v-for="element in elements" v-html="element.render()">
</template>
</div>
`,
In a perfect world, I would do something like this, but mustache format can't output html:
<div id="container">
<template v-for="(element, index) in elements">
{{ element.render() }}
</template>
</div>
The desired output:
<div id="container">
(output of element.render())
(output of element.render())
(output of element.render())
etc.
</div>
Thank you for any guidance,
L
Delete the <template> tag completely and use render function in <script> tag. Something like this, but this code is not tested, demonstrational only.
export default {
...
render (h) {
return h('div', this.elements.map(function (elm) {
return h(elm.render())
})
}
}

Dynamic templates in Meteor

I have a question about dynamic templates in Meteor. I want to use a template multiple times with other data, but have no idea what I am doing wrong.
I have the following template:
<template name="knowyourcompany">
<section>
<header>
<h2>Know your company</h2>
</header>
<p class="question">Q: {{question}}</p>
</section>
</template>
I have the following template helper:
Template.knowyourcompany.helpers({
knowyourcompany1: function(){
return { question: "test" }
}
});
And this is how I include the template
{{> Template.dynamic template="knowyourcompany" data=knowyourcompany1}}
The problem is that I see an empty template. What am I doing wrong?
Template.dynamic should be used if you want the template to be interchangeable, but in your case you want your data context to be interchangeable, so this is no use case for Template.dynamic. Instead, use the ordinary {{> templateName myDataContext}}:
<template name="myMainTemplate">
{{> myTemplate myDataContext}}
</template>
Template.myMainTemplate.helpers({
myDataContext: function(){
return {a: 1, b: 2}
}
})
<template name="myTemplate">
a: {{a}}, b: {{b}}
</template>