Boostrap Vue Change Cell Text in B-table - javascript

In a b-table, each cell in a column should have different text color. Not the background of the cell, but the actual text color. I am able to change the text color of the column header, but not the individual cell texts.
The b-table code:
<b-card title="Total">
<b-table sticky-header="600px" hover :items="total" :fields="groupByFields"></b-table>
</b-card>
The fields where the column header color change is:
groupByFields: [
{
key: 'name',
sortable: true,
},
{
label: 'Total',
key: 'count',
},
{
key: 'interested',
thStyle: { color: '#3eef33' },
sortByFormatted: false,
formatter: (value) => {
const res = value;
return (`($${res})`);
},
},
],
The thStyle color attribute changes the header text color, but not the text of values in the cells of that column. How would I match that color to the cell text (not background) of that column too?

Here's an example, using the #cell() slot:
new Vue({
el: '#app',
data: () => ({
items: [],
fields: [
'id',
{ key: 'title', style: { fontStyle: 'italic' } },
{ key: 'price', style: { color: 'red', textAlign: 'right' } }
]
}),
mounted() {
fetch('https://dummyjson.com/products')
.then(_ => _.json())
.then(_ => this.items = _.products);
}
})
<link href="https://unpkg.com/bootstrap#4.6.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.22.0/dist/bootstrap-vue.css" rel="stylesheet"/>
<script src="https://unpkg.com/babel-polyfill/dist/polyfill.min.js"></script>
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.22.0/dist/bootstrap-vue.js"></script>
<div id="app">
<b-table :items="items" :fields="fields">
<template #cell()="{field, value}">
<div :style="field.style" v-text="value" />
</template>
</b-table>
</div>
If you only need to style a few columns, you might want to use appropriate #cell({key}) slots.
Documentation here.
Probably the most common technique is to add classes to cells. It provides granular control over the applied styling, without sacrificing flexibility.

Related

Tooltip disappears after disabling and re-enabling BootstrapVue table column

I have a BootstrapVue table which looks like this;
When you hover your mouse on the First Name column, the tooltip Tooltip for First name will appear. The checkboxes at the top will cause the corresponding table columns to appear/disappear.
Here's the description of the bug I encounter.
I uncheck First Name checkbox. Column First Name disappears. Now, I recheck the First Name checkbox. Column First Name reappears again. This is fine. However, the tooltip no longer works when I hover my mouse on the First Name column.
Here's the complete code in a single HTML file.
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.2.2/dist/bootstrap-vue.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.2.2/dist/bootstrap-vue.min.js"></script>
<div id='app'>
<b-checkbox
:disabled="visibleFields.length == 1 && field.visible"
v-for="field in showFields"
:key="field.key"
v-model="field.visible"
inline
>
{{ field.label }}
</b-checkbox>
<b-table :items="items" :fields="visibleFields" bordered>
</b-table>
<b-tooltip target="HeaderFirst" triggers="hover" container="HeaderFirst">
Tooltip for First name<br>
</b-tooltip>
</div>
<script>
new Vue({
el: '#app',
computed: {
visibleFields() {
return this.fields.filter(field => field.visible)
},
showFields() {
return this.fields.filter(field => field.key.includes('first') || field.key.includes('last'))
}
},
data: dataInit,
})
function dataInit() {
let init_data = {};
init_data.fields = [
{ key: 'id', label: 'ID', visible: true },
{ key: 'first', label: 'First Name', visible: true,
thAttr: {
id: "HeaderFirst"
},
},
{ key: 'last', label: 'Last Name', visible: true },
{ key: 'age', label: 'Age', visible: true },
];
init_data.items = [
{ id: 1, first: 'Mike', last: 'Kristensen', age: 16 },
{ id: 2, first: 'Peter', last: 'Madsen', age: 52 },
{ id: 3, first: 'Mads', last: 'Mikkelsen', age: 76 },
{ id: 4, first: 'Mikkel', last: 'Hansen', age: 34 },
];
return init_data;
}
</script>
I am using vue v2.6, BootstrapVue.
The <b-tooltip>'s target must exist in the DOM upon mounting. It does not dynamically attach a new tooltip to newly created elements in the DOM.
The first run of your code shows a tooltip because <b-table> initially contains the #HeaderFirst element. When you uncheck the First Name box, the existing elements in <b-table> are replaced with new elements via the computed property. The elements removed from the DOM include the one that <b-tooltip> initially attached a tooltip to, and no new tooltip is generated after <b-table> was updated.
Solution
One solution is to render <b-tooltip> only when the target element is visible:
Create a computed prop that determines whether the #HeaderFirst field is visible.
Conditionally render <b-tooltip> based on that computed prop.
new Vue({
computed: { 1️⃣
firstNameHeaderVisible() {
return this.fields.find(field => field.thAttr?.id === 'HeaderFirst')?.visible
}
},
⋮
})
2️⃣
<b-tooltip target="HeaderFirst" v-if="firstNameHeaderVisible">
demo
On disabled elements event hover is not working. You need to wrap element by other e.g. div where you set up tooltip and then it should work well

API JSON formatting to bootstrap-vue table

I am a student working on a VueJS dashboard displaying scientific research data from clinicaltrials.gov. The issue is the JSON response value is in the form of an array and as such prints with brackets and quotation marks in the table. I am trying to display the data without the brackets and quotations. In addition, when there are multiple items as in the Condition field, I am trying to display each item seperated by commas.
The data comes from the API as an axios response in the following format:
{
"StudyFieldsResponse":{
"APIVrs":"1.01.02",
"DataVrs":"2021:03:18 22:20:36.369",
"Expression":"Pfizer",
"NStudiesAvail":371426,
"NStudiesFound":5523,
"MinRank":1,
"MaxRank":1,
"NStudiesReturned":1,
"FieldList":[
"OrgFullName",
"Acronym",
"InterventionName",
"Condition",
"Phase",
"LastKnownStatus",
"ResultsFirstPostDate",
"LastUpdatePostDate"
],
"StudyFields":[
{
"Rank":1,
"OrgFullName":[
"Pfizer"
],
"Acronym":[],
"InterventionName":[
"Infliximab [infliximab biosimilar 3]"
],
"Condition":[
"Crohn's Disease",
"Ulcerative Colitis"
],
"Phase":[],
"LastKnownStatus":[],
"ResultsFirstPostDate":[],
"LastUpdatePostDate":[
"December 21, 2020"
]
}
]
}
}
Axios Call in mounted:
var APIurl = "https://clinicaltrials.gov/api/query/study_fields?expr=" + TrialSearch + "%0D%0A&fields=OrgFullName%2CAcronym%2CInterventionName%2CCondition%2CPhase%2CLastKnownStatus%2CResultsFirstPostDate%2CLastUpdatePostDate&min_rnk=1&max_rnk=999&fmt=json"
axios
.get(APIurl)
.then(response => {this.items = response.data.StudyFieldsResponse.StudyFields})
.catch(function (error) {
//eslint-disable-next-line no-console
console.log(error);
})
HTML of b-table:
<b-table :items="items" id="table-list" responsive :per-page="perPage" :current-page="currentPage" :fields="fields" :sort-by.sync="sortBy" :sort-desc.sync="sortDesc" >
</b-table>
js data function:
data: function() {
return {
sortBy: 'name',
perPage: 10,
PipeperPage: 5,
currentPage: 1,
sortDesc: false,
sortByFormatted: true,
filterByFormatted: true,
sortable: true,
fields: [
{ key: 'Acronym', sortable: true },
{ key: 'InterventionName', sortable: true },
{ key: 'Condition', sortable: true },
{ key: 'Phase', sortable: true },
{ key: 'LastKnownStatus', sortable: true },
{ key: 'ResultsFirstPostDate', sortable: true },
{ key: 'LastUpdatePostDate', sortable: true },
],
items: [],
screenshot of current table format
Sorry for the long post; I am really trying to learn but am lost.
Like you said all the properties of the response are arrays instead of primitives values like string, boolean, or number. b-table expects that the items data is an array of rows (that is good) with properties and primitives values like you are passing arrays is printing the whole value as a string.
What you need to do is add a computed property (that will change when items change) mapping that item with a new item, choosing the values you use (to keep it simple) and joining the array in a string.
You can choose to join the array arr.join(', ') or to select the first item arr[0] || "".
Life example https://codesandbox.io/s/api-json-formatting-to-bootstrap-vue-table-r91cw?file=/src/components/Dashboard.vue:1095-1586
<template>
<b-table
:items="simpleItems"
id="table-list"
responsive
:per-page="perPage"
:current-page="currentPage"
:fields="fields"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
/>
</template>
<script>
export default {
data() {
return {
// your data
}
},
computed: {
simpleItems() {
return this.items.map((item) => {
return {
Acronym: item.Acronym.join(", "),
InterventionName: item.InterventionName.join(", "),
Condition: item.Condition.join(", "),
Phase: item.Phase.join(", "),
LastKnownStatus: item.LastKnownStatus.join(", "),
ResultsFirstPostDate: item.ResultsFirstPostDate.join(", "),
LastUpdatePostDate: item.LastUpdatePostDate.join(", "),
};
})
},
}
}
</script>

#click doesn't work when rendering it using v-html

I have a component which I wish I could create rows and columns with data and columns object from the parent.
There is a situation where I need to render an html template to create a clickable link, I make it want to display the link in the Actions column. vue version: ^2.5.17
below is the code from parent.vue:
// parent.vue
<table-component :data="category" :column="column" class="bg-red-200 py-4 mt-8"/>
// parent.vue
data(){
return {
modalMode: '',
isShowModalEdit: false,
category: [
{ id: 1, name: 'Jasa Pre-Order', color: '#eb4034' },
{ id: 2, name: 'Jualan', color: '#fcba03' },
{ id: 3, name: 'Jasa Design', color: '#9f34eb' },
],
}
}
// parent.vue
methods: {
toggleModalEdit(){
this.isShowModalEdit = !this.isShowModalEdit
this.modalMode = 'EDIT'
}
}
// parent.vue
computed: {
column() {
return [
{
dataField: 'name',
text: 'Name',
},
{
dataField: 'color',
text: 'Category Color',
formatter: (cell,row) => {
return `
<div style="background-color: ${cell};" class="rounded-full h-8 w-8 flex items-center justify-center mr-2"></div>
<div class="font-bold text-gray-500">${cell}</div>
`
},
classes: (cell, row, rowIndex, colIndex) => {
return 'flex';
}
},
{
dataField: 'actions',
text: 'Actions',
formatter: (cell,row) => {
return `
Edit
`
},
},
]
},
}
and this is the sample code from component.vue:
// component.vue
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="(row, rowIndex) in data" :key="rowIndex">
<td v-for="(col, colIndex) in column" :key="col.dataField" :class=" col.classes ? col.classes(row[col.dataField],row,rowIndex,colIndex) : '' " v-html=" col.formatter ? col.formatter(row[col.dataField],row) : row[col.dataField] " class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"></td>
<tr>
</tbody>
// component.vue
props: {
data: {
type: Array,
required: true
},
column: {
type: Array,
required: true
},
}
the result is like this:
but the link in the Actions column does not work as it should, I hope that when clicking this link will run the method of the perent namely toggleModalEdit. and this is what the link looks like when i inspect it:
i am still new to vue, i am not sure what i did best or not, i hope you guys can help.
Your issue is, that the HTML inside the v-html directive is not processed by Vue's template compiler at all.
Because the compiler doesn't compile this HTML, the #click is not interpreted (but simply rendered without triggering any action).
For this to work, you'd need to iterate over all columns and initialize a new component that handles what's inside the cell yourself directly in HTML (and not in some string that's gonna be rendered later on).
I guess that this is enough - if you still need to interpret what's in the string, you may use Vue.compile to interpret the content. But be careful as it's not safe in case there's some malicious code in it - but since the directive by default has no sanitizing at all, I guess that's just the way Vue.js works.
Thanks to #SimplyComple0x78 for the answer, I marked your suggestions:
For this to work, you'd need to iterate over all columns and initialize a new component that handles what's inside the cell yourself directly in HTML (and not in some string that's gonna be rendered later on).
so I try to create and initialize a new component, I call it element-generator. reference from here. here's the code:
// element-generator.vue
<script>
export default {
render: function (createElement) {
const generatedChildren = (child) => {
if(!child) return // when child of undefined
if(typeof child === 'string') return child // when children is String
return child.map((e,i,a)=>{
if(typeof child[i] == 'string'){
return child[i]
}else{
return createElement(
child[i].tag,
child[i].attributes,
generatedChildren(child[i].children) // javascript recursive
)
}
})
}
return createElement(
this.formatter.html.tag,
this.formatter.html.attributes,
generatedChildren(this.formatter.html.children)
)
},
props: {
formatter: {
type: Object,
required: true
},
},
}
</script>
and I no longer use v-html in component.vue instead I just do a check inside <td> and call element-generator to handle what's inside the cell:
// component.vue
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="(row, rowIndex) in data" :key="rowIndex">
<td v-for="(col, colIndex) in column" :key="col.dataField"
:class=" col.classes ? col.classes(row[col.dataField],row,rowIndex,colIndex) : '' "
class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"
>
<element-generator v-if="col.formatter" :formatter="col.formatter(row[col.dataField],row)"></element-generator>
<div v-else>{{row[col.dataField]}}</div>
</td>
</tr>
</tbody>
and in parent.vue I replaced the String with the Object that will be passed to the element-generator later, it looks like this:
// parent.vue
computed: {
column() {
return [
{
dataField: 'name',
text: 'Name',
},
{
dataField: 'color',
text: 'Category Color',
formatter: (cell,row) => {
return {
html: {
tag: 'div',
attributes: {
class: 'flex'
},
children:[
{
tag: 'div',
attributes: {
style: `background-color: ${cell};`,
class: 'rounded-full h-8 w-8 flex items-center justify-center mr-2',
},
},
{
tag: 'div',
attributes: {
class: 'font-bold text-gray-500',
},
children: cell
},
]
}
}
},
},
{
dataField: 'actions',
text: 'Actions',
formatter: (cell,row) => {
return {
html: {
tag: 'a',
attributes: {
class: 'text-indigo-600 hover:text-indigo-900',
on: {
click: this.toggleModalEdit
},
attrs: {
href: "#"
},
},
children: 'Edit'
},
}
},
},
]
},
},
then when I inspect it in the browser, the result is like this(this is different from the previous one):
and finally what I want to display when Edit is clicked is now displayed:
Thank you very much everyone.

Checkboxes for Hide/Show columns in Vue.js table not working properly

I made a vue.js bootstrap table for loading some data from local JSON files.
I'm trying to implement show/hide columns via checkboxes.
I think I've solved most of the problem, but the problem is when I hide a column and then press on that same checkbox again (to make column visible again) I lose the order of table (that column becomes last column) and so on.
For example if I hide "Timestamp" column which is first table header in my table and then press to show it again it is no longer on first place, instead it gets created on last place.
https://imgur.com/BaTfgci --> this is how app looks right now
https://codepen.io/frane_caleta/pen/KKPMKrL --> codepen of my code, you won't be able to load it without JSON file though
https://imgur.com/a/23jx0lZ --> JSON data example
First time asking question here, so feel free to ask me if you need some more information to solve the problem :)
<b-form-group label="Hide columns: ">
<b-form-checkbox-group id="checkbox-group-1" v-model="selected" :options="fields" name="flavour-1">
</b-form-checkbox-group>
</b-form-group>
//my table
<b-table id="myTable"
:small="small"
:bordered="bordered"
hover head-variant="dark"
stacked="md"
:items="cptItems"
:fields="selected"
:current-page="currentPage"
:per-page="perPage"
:filter="filter"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
#filtered="onFiltered"
:tbody-tr-class="rowClass"
v-if="selected.length > 0">
</b-table>
//Javascript file
function initializeVue() {
return new Vue({
el: '#app',
data() {
return {
items: data.logdatas,
selected: [],
fields: [{
text: 'Origin',
value: {
key: 'origin',
label: 'Origin',
sortable: true,
class: 'text-center',
index: 0
}
},
{
text: 'Timestamp',
value: {
key: 'timeStamp',
label: 'Timestamp',
sortable: true,
class: 'text-center',
index: 1
}
},
{
text: 'Level',
value: {
key: 'level',
label: 'Level',
sortable: true,
class: 'text-center',
index: 2
}
}, ...there are 4 more fields here like this...
//my method for creating those checkboxes
created() {
this.selected = this.fields.map(field => field.value);
}
the selected data is your culprit. b-checkbox-group :selection lists items in order of selection.
example2
b-table :fields lists columns in the order of the items.
better make a static fields-list and filter by selection.
// make this data or property
let columnNames = ["one", "two", "three", "infinity", "pi"];
// make this data
let selected = []
//make this computed // can be optimized
let activeFields = columNames.filter(name => selected.includes(name))
// more like this
export default {
data(){
return {
selected: [],
columnNames: ['name1', 'name2']
},
computed(){
activeColumns(){
return this.columnNames.filter(this.selected.includes) || []
}
}
const app = new Vue({
data(){
return {
currentPage: 0,
perPage: 10,
fields: ['age', 'first_name', 'last_name'],
//selected: [],
selected: ['age', 'first_name', 'last_name'],
items: [
{ age: 40, first_name: 'Dickerson', last_name: 'Macdonald' },
{ age: 21, first_name: 'Larsen', last_name: 'Shaw' },
{ age: 89, first_name: 'Geneva', last_name: 'Wilson' },
{ age: 38, first_name: 'Jami', last_name: 'Carney' }
]
}
},
computed: {
activeFields(){
return this.fields.filter(name => this.selected.includes(name))
}
}
}).$mount("#app");
<!-- Add this to <head> -->
<!-- Load required Bootstrap and BootstrapVue CSS -->
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<!-- Load polyfills to support older browsers -->
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<!-- Load Vue followed by BootstrapVue -->
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<div id="app">
<b-form-group label="Hide columns: ">
<b-form-checkbox-group id="checkbox-group-1" v-model="selected" :options="fields" name="flavour-1">
</b-form-checkbox-group>
</b-form-group>
<b-table id="myTable"
:bordered="true"
hover head-variant="dark"
stacked="md"
:items="items"
:fields="selected"
:current-page="currentPage"
:per-page="perPage"
tbody-tr-class="row-class"
v-if="selected.length > 0">
</b-table>
<b-table id="myTable"
:bordered="true"
hover head-variant="dark"
stacked="md"
:items="items"
:fields="activeFields"
:current-page="currentPage"
:per-page="perPage"
tbody-tr-class="row-class"
v-if="selected.length > 0">
</b-table>
</div>

How to add a style element inside a cell when the text inside changes to another value

I'm working on a ReactJS app which shows from an API a table populated of flights information. One of the columns is called status and it is changing value when the status of flight changes.
For example, we have a flight in departures and the status now is "to gate" but later is "Boarding"
What I would like to achieve is to add a style element like for To gate a color or icon but when boarding a different color and so on. So I would like to change the cell based on what will be the status value.
I don't know how to do that as I'm new to React. I was thinking on the component below on the row/cell of status maybe it is possible to add an if which changes based on the value. Don't know if it possible.
My component looks like this:
class FlightComponent extends React.Component {
render() {
const { data, activeTab } = this.props;
let columns = [
//{ Header: 'Date', accessor: 'date' },
{ Header: 'Time', accessor: 'time' },
{ Header: 'Expected', accessor: 'expected', Cell: (row) => (<div className="expected">{row.value}</div>) },
{ Header: 'Airline', accessor: 'airline', Cell: (row) => (<div className="airline-name">{row.value}</div>) },
{ Header: 'Flight No.', accessor: 'flight_no'},
];
if (activeTab == 1) {
columns.push({ Header: 'Destination', accessor: 'destination' })
} else {
columns.push({ Header: 'Arriving From', accessor: 'arriving_from' })
}
columns = columns.concat([
{ Header: 'Gate', accessor: 'gate' },
{ Header: 'Terminal', accessor: 'terminal' },
{ Header: 'Status', accessor: 'status' }
]);
return (
<div>
<ReactTable
columns={ columns }
data = {this.props.data}
/>
</div>
)
}
}
For react-table you could use Custom Cell, Header and Footer Rendering
You can use any react component or JSX to display content in column headers, cells and footers. Any component you use will be passed the following props (if available):
row - Original row from your data
original - The post-accessed values from the original row
index - The index of the row
viewIndex - the index of the row relative to the current page
level - The nesting depth (zero-indexed)
nestingPath - The nesting path of the row
aggregated - A boolean stating if the row is an aggregation row
subRows - An array of any expandable sub-rows contained in this row
You can check, here is wokring stackblitz demo.
Code Snippet
render() {
const data = [{
task: 'Demo 1',
status: 'pending'
}, {
task: 'Demo 2',
status: 'completed'
}]
const columns = [{
Header: 'Task Name',
accessor: 'task'
}, {
Header: 'Status',
accessor: 'status',
Cell: (row, original, index, viewIndex, level, nestingPath, aggregated, subRows) => {
row.styles['color'] = '#fff';
row.styles['backgroundColor'] = row.value == 'pending' ? 'red' : 'green';
return row.value.toUpperCase();
}
}];
return (
<ReactTable
data={data}
columns={columns}
defaultPageSize={5}
className="-striped -highlight"
/>
);
}

Categories

Resources