How to show an empty row in BootstrapVue's b-table - vue.js

I am using a b-table from BootstrapVue. However, when the array of objects for my table is empty, i want to show the table headers + 1 empty row, without any text in the cells of the table.
On BoostrapVue's b-table page, there is some explanation about showing something for an empty table, but i'm not following the explanation. There is also no visual of the outcome.
When using show-empty, it gives a simple text 'no records ...', but i don't want the text. Or maybe i want a custom text (depending on the language when using i18n).
So the html is currently:
<b-table bordered :head-variant="'light'" hover :items="dogs" :fields="dogHeaders" >
</b-table>
And in the vue data i have:
dogs: [], //because i want to mimic an empty table
dogHeaders: ['name','color','age'],
Can someone show me an example how to do this?
edit: i currently see only a workaround with populating my dogs array with an empty dog like so:
dogs: [{name:'',color:'',age:''}],
But i guess there should be a better way (+ this workaround gives a row that has less height than an actual filled row)

You could use b-table with show-empty attribute and slots. I prepared a code snippet to show this:
new Vue({
el: '#app',
data: {
dogs: [],
dogHeaders: ['name', 'color', 'age'],
}
})
<link href="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap#5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.js"></script>
<div id="app">
<b-table bordered :head-variant="'light'" hover :items="dogs" :fields="dogHeaders" show-empty>
<template #empty="scope">
Empty row
</template>
</b-table>
</div>

I think the example is right there in the docs. They provide slots for exactly this use case, where you can put anything you want:
<b-table bordered :head-variant="'light'" hover :items="dogs" :fields="dogHeaders" show-empty>
<template #empty="scope">
Whatever HTML you put here will be shown when the table is empty
</template>
</b-table>

Since you want to render actual cells, you can use the top-row slot instead of the standard empty slot.
You can then conditionally render the slot using a v-if, based on whether the dogs array is empty or not.
Then render empty cells (<td>) inside the slot for each element in your dogHeaders array.
new Vue({
el: '#app',
data() {
return {
dogs: [],
dogHeaders: ['name', 'color', 'age']
}
},
methods: {
addRow() {
this.dogs.push({
name: 'Pence',
color: 'Black',
age: 53
})
}
}
})
<link href="https://unpkg.com/bootstrap#4.5.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.js"></script>
<div id="app" class="p-3">
<b-btn #click="dogs = []">Reset Table</b-btn>
<b-btn #click="addRow">Add row</b-btn>
<hr />
<b-table bordered head-variant="light" hover :items="dogs" :fields="dogHeaders">
<template #top-row v-if="dogs.length === 0">
<!-- Adding to the cell so that it maintains the standard cell height -->
<td v-for="i in dogHeaders.length"> </td>
</template>
</b-table>
</div>

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>

Ant Design Vue - Custom Header in Collapse Component

Hello I'am using Vue and Ant Design. So i pick Collapse Component from Ant Design: https://antdv.com/components/collapse/
My problem is that in Collapse.Panel there is a prop header which is string, but i need to put string in there (example: v0.6.1.11) and date, so i can add margin-left: auto or something like that in date.
Exmaple:
I tried using white-space: pre and then simply add spaces to achive that space between but it's not responsive solution.
How can i pass into header prop pure html so i could put something like two <p> tags there and then create space between title and date?
As per docs, we can make use of Panel property extra slot.
Run this sample code
new Vue({
el: "#app",
data() {
return {
text: `A dog is a type of domesticated animal.Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.`,
activeKey: ['1'],
expandIconPosition: 'left',
};
},
computed:{
currDate(){
return new Date().toLocaleDateString()
}
}
});
.ant-collapse>.ant-collapse-item>.ant-collapse-header{
display:flex;
justify-content:space-between
}
<link rel="stylesheet" href="https://unpkg.com/ant-design-vue#1.4.11/dist/antd.min.css">
<script src="https://unpkg.com/vue#2.6.11/dist/vue.min.js"></script>
<script src="http://bms.test/vendor/laravel-admin/moment/min/moment-with-locales.min.js"></script>
<script src="https://unpkg.com/ant-design-vue#1.5.0/dist/antd.min.js"></script>
<div id="app">
<a-collapse v-model="activeKey">
<a-collapse-panel key="1" header="This is panel header 1">
<p>{{ text }}</p>
<div slot="extra">{{currDate}}</div>
</a-collapse-panel>
<a-collapse-panel key="2" header="This is panel header 2" :disabled="false">
<p>{{ text }}</p>
<div slot="extra">{{currDate}}</div>
</a-collapse-panel>
<a-collapse-panel key="3" header="This is panel header 3" disabled>
<p>{{ text }}</p>
<div slot="extra">{{currDate}}</div>
</a-collapse-panel>
</a-collapse>
</div>
You can use the below code for the pupose.
<a-collapse-panel key="1" header="heading 1">
<p>Content</p>
<template #extra><p>heading 2</p></template> <!-- for newer version -->
<p slot="extra">heading 2</p> <!-- for older version -->
</a-collapse-panel>

Vue and Bootstrap Vue - dynamically use slots

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>

How to add a class and then remove that same class in the vuejs?

I have the following div:
<div class="col bg-color-red-4">
<a href="#" v-on:click="showMobileMenu = !showMobileMenu">
<i class="hamburger hamburger--3dx " v-bind:class="{ 'is-active': showMobileMenu }" >
<span class="hamburger-box">
<span class="hamburger-inner"></span>
</span>
</i>
</a>
<p>3DX</p>
</div>
data(){
return {
showMobileMenu: false
}
},
That is visually like this:
basically it's a button that allows me to open the left panel:
When you click on the button you add the class 'is-active' that allows the change of form, leaving it like this:
and it effectively opens the left panel, but after turning off the left panel, that is, returning to the view that I was in, this one finds the class added.
then I need that when I click, I change the shape to an "X", when I open the left panel, but when I come back I do not have the form "X", that is, it does not contain the class 'is-active' anymore
Beforehand thank you very much.
At first read the official docs:
[https://v2.vuejs.org/v2/guide/class-and-style.html#ad][1]
At second - check Matt Oestreich's answer
At third - you can also bind classes to the variables like this:
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
/>
And in a Vue.js script:
data: {
isActive: true,
hasError: false
}
You can bind to the class attribute and supply specific classes that way..
[CodePen Mirror]
Code Snippet:
const vm = new Vue({
el: "#app",
data: {
status: false,
bars: "fa fa-bars",
times: "fa fa-times",
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.5.14/vuetify.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/font-awesome#4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.5.6/dist/vuetify.min.css" rel="stylesheet" />
<div id="app">
<v-app>
<v-content>
<v-container>
<i
style="cursor:pointer;"
:class="status ? times : bars"
#click="status = !status"
></i>
<h1>Click The Icon</h1>
<h3>It is currently {{ status ? 'open' : 'closed' }}</h3>
</v-container>
</v-content>
</v-app>
</div>

Can't get the correct value of scrollHeight for a textarea - VueJs, Vuetify

Am trying to get the value scrollHeight of a texteara filed, the problem is when i use the document object : document.querySelector("#textarea-element").scrollHeight, i have a correct value ,
but when i’ve tried to do it with refs the value is wrong and didn’t change.
I’ve made a detailed jsfiddle for these behaviors please see below:
new Vue({
el: '#app' ,
data: {
height: 'auto',
scrollHeightUsingRefsVuejs:'',
scrollHeightUsingDocumentObject: ''
},
methods:{
resizeTextarea (e) {
this.scrollHeightUsingRefsVuejs = this.$refs.messageBox.$el.scrollHeight
this.scrollHeightUsingDocumentObject = document.querySelector("#textarea-element").scrollHeight
console.log(this.scrollHeightUsingRefsVuejs, this.scrollHeightUsingDocumentObject)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link href="https://unpkg.com/vuetify#1.0.11/dist/vuetify.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/vuetify#1.0.11/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-content>
<v-container>
<v-text-field
color="cyan darken"
label="Text field"
multi-line
rows="2"
placeholder="Start typing..."
ref="messageBox"
#keydown.native="resizeTextarea"
:style="{height: height}"
id="textarea-element"
></v-text-field>
<p>
Scroll Height Using Refs Vuejs : <strong>{{ scrollHeightUsingDocumentObject}}</strong>
</p>
<p>
<span style="color:red">(Wrong)</span> Scroll Height Using Refs Vuejs : <strong>{{ scrollHeightUsingRefsVuejs }}</strong>
</p>
</v-container>
</v-content>
</v-app>
</div>
(To see the value of scrollHeight type anything if the textearea filed)
The this.$refs.messageBox.$el.scrollHeight refers to parent node input-group generated by Vuetify that’s way the value seems wrong , all we need just to add a selector to tearea node like this this.$refs.messageBox.$el.querySelector('textarea').scrollHeight