So I'm using Bootstrap Vue with this test app. I'm trying to change the variant of a table cell depending on the value of it. Unfortunately, the variant parameter will not take a function, so I'm out of ideas on how to achieve this.
This is my code:
var app = new Vue({
el: '#app',
data: {
items: [], //Will be populated through AJAX
fields: [
{
key: 'Vendedor',
label: 'Vendedor'
},
{
key: 'OBJETIVO',
label: 'Objetivo',
formatter: (value) => { return parseFloat(value).toFixed(2)},
variant: estiloObjetivo //THIS IS NOT WORKING
}
]
},
methods: {
Cargar: function () {
var salesperson = getCookie('salespersonCode');
var url_servicio = 'http://MywebService/';
var self = this;
$.ajax({
type: 'GET',
url: url_servicio + 'ventas/' + salesperson,
dataType: "json", // data type of response
success: function(data){
self.items = data
}
});
},
estiloObjetivo (value) {
if value > 0 //I need my cell variant to change depeding on this value
return 'danger'
else
return 'success'
}
}
})
This is my HTML part:
<div id="app">
<button v-on:click="Cargar">Cargar</button>
<b-table striped hover :fields="fields" :items="items"></b-table>
</div>
Any ideas on how to style a Bootstrap-vue cell dynamically?
This is the way it's done in the docs, it's actually set in the "items" array, but how is this useful in cases like mine where I get the data from a web service?:
{
salesperson: 'John',
Objetivo: 2000,
_cellVariants: { salesperson: 'success', Objetivo: 'danger'}
},
So I guess what I need is a way to set the I need is to set the _cellVariants parameter of each element in the 'items' array.
You likely need a computed property. Computed properties automatically update on changes to the reactive variables that they depend on.
The following example implements a computed property, styledItems, which you must use in place of items in the template. It returns a 1-deep copy of items, i.e. a new array containing a copy of each item, with the extra _cellVariants property added.
new Vue({
data: {
items: [ /* your data here */ ]
},
methods: {
estiloObjetivo: value => (value > 0) ? 'danger' : 'success'
},
computed: {
styledItems() {
return this.data.map(datum =>
Object.assign({}, datum, {
_cellVariants: {
Objetivo: this.estiloObjetivo(datum.Objetivo)
}
})
}
})
If you want to add variant to items you could use a computed property called cptItems and define it as follows:
computed:{
cptItems(){
return this.items.map((item)=>{
let tmp=item;
item.OBJETIVO>0?tmp.variant='danger':tmp.variant='success';
return tmp;
})
}
and use that property inside your template like :
<b-table .... :items="cptItems"></b-table>
I was sure the answers above would solve my own issue but they did not. I found a different way to color table cells: https://github.com/bootstrap-vue/bootstrap-vue/issues/1793
This is aside from using variants to color a table cell. Instead, we utilize tdclass and a function.
<script>
new Vue({
el: '#itemView',
data() {
return {
fields: [
{
key: 'Objetive',
sortable: true,
thClass: 'text-nowrap',
tdClass: (value, key, item) => {
return 'table-' + this.getColor(item);
}
}
],
};
},
methods: {
getColor(item) {
return item.Objetive > 0 ? 'danger' : 'success';
},
},
});
</script>
For my own use-case, I needed to compare two cells of the same row, then apply a class to one.
...
{
key: 'DEMAND_QTY',
sortable: true,
thClass: 'text-nowrap',
tdClass: (value, key, item) => {
return 'table-' + this.demandStatusColor(item);
},
},
{ key: 'TOTAL_DEMAND', sortable: true, thClass: 'text-nowrap' },
],
};
},
methods: {
demandStatusColor(item) {
return item.DEMAND_QTY < item.TOTAL_DEMAND ? 'danger' : 'success';
},
}
...
Perhaps this will help someone, if not OP.
#John answer worked for me. I don't have enough reputation to make comment or useful
tdClass: (type, key, item) => {
switch (type) {
case "value":
return "bg-warning text-white";
break;
case "value":
return "bg-danger text-white";
break;
case "value":
return "bg-info text-white";
break;
default:
break;
}
},
Related
the expand/collapse part of this works just fine.
Right now I am using javascript startInterval() to reload the table every 2 seconds. Eventually this will be moving to web sockets.
In general, as part of the table load/reload, the system checks to see if it should display the icon " ^ " or " v " in the details column by checking row.detailsShowing, this works fine.
getChevron(row, index) {
if (row.detailsShowing == true) {
return "chevronDown";
}
return "chevronUp";
}
When the user selects the " ^ " icon in the relationship column, #click=row.toggleDetails gets called to expand the row and then the function v-on:click="toggleRow(row)" is called to keep track of which row the user selected. This uses a server side system generated guid to track.
Within 2 seconds the table will reload and the row collapses. On load/reload, in the first column it loads, relationship, I call a function checkChild(row), to check the row guid against my locally stored array, to determine if this is a row that should be expanded on load.
<template #cell(relationship)="row"> {{checkChild(row)}} <\template>
if the row guid matches one in the array I try setting
checkChild(row){
var idx = this.showRows.indexOf( row.item.id);
if(idx > -1){
row.item.detailsShowing = true;
row.rowSelected = true;
row.detailsShowing == true
row._showDetails = true;
}
}
and I am able to see that i have found match, but none of those variables set to true keeps the expanded row open, the row always collapses on reload
anyone have any ideas as to how i can make the row(s) stay open on table reload?
The issue with your code is because of a Vue 2 caveat. Adding properties to objects after they've been added to data will not be reactive. To get around this you have to utilize Vue.set.
You can read more about that here.
However, calling a function like you are doing in the template seems like bad practice.
You should instead do it after fetching your data, or use something like a computed property to do your mapping.
Here's two simplified examples.
Mapping after API call
{
data() {
return {
items: [],
showRows: []
}
},
methods: {
async fetchData() {
const { data } = await axios.get('https://example.api')
foreach(item of data) {
const isRowExpanded = this.showRows.includes(item.id);
item._showDetails = isRowExpanded;
}
this.items = data;
}
}
}
Using a computed
{
computed: {
// Use `computedItems` in `<b-table :items="computedItems">`
computedItems() {
const { items, showRows } = this;
return items.map(item => ({
...item,
_showDetails: .showRows.includes(item.id)
}))
}
},
data() {
return {
items: [],
showRows: []
}
},
methods: {
async fetchData() {
const { data } = await axios.get('https://example.api')
this.items = data;
}
}
}
For a more complete example, check the snippet below.
const {
name,
datatype,
image
} = faker;
const getUser = () => ({
uuid: datatype.uuid(),
personal_info: {
first_name: name.firstName(),
last_name: name.lastName(),
gender: name.gender(),
age: Math.ceil(Math.random() * 75) + 15
},
avatar: image.avatar()
});
const users = new Array(10).fill().map(getUser);
new Vue({
el: "#app",
computed: {
computed_users() {
const {
expanded_rows,
users
} = this;
return users.map((user) => ({
...user,
_showDetails: expanded_rows[user.uuid]
}));
},
total_rows() {
const {
computed_users
} = this;
return computed_users.length;
}
},
created() {
this.users = users;
setInterval(() => {
users.push(getUser());
this.users = [...users];
}, 5000);
},
data() {
return {
per_page: 5,
current_page: 1,
users: [],
fields: [{
key: "avatar",
class: "text-center"
},
{
key: "name",
thClass: "text-center"
},
{
key: "personal_info.gender",
label: "Gender",
thClass: "text-center"
},
{
key: "personal_info.age",
label: "Age",
class: "text-center"
}
],
expanded_rows: {}
};
},
methods: {
onRowClicked(item) {
const {
expanded_rows
} = this;
const {
uuid
} = item;
this.$set(expanded_rows, uuid, !expanded_rows[uuid]);
}
}
});
<link href="https://unpkg.com/bootstrap#4.5.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.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>
<script src="https://unpkg.com/faker#5.5.3/dist/faker.min.js"></script>
<div id="app" class="p-3">
<b-pagination v-model="current_page" :per-page="per_page" :total-rows="total_rows">
</b-pagination>
<h4>Table is refresh with a new item every 5 seconds.</h4>
<h6>Click on a row to expand the row</h6>
<b-table :items="computed_users" :fields="fields" bordered hover striped :current-page="current_page" :per-page="per_page" #row-clicked="onRowClicked">
<template #cell(avatar)="{ value }">
<b-avatar :src="value"></b-avatar>
</template>
<template #cell(name)="{ item: { personal_info: { first_name, last_name } }}">
{{ first_name }} {{ last_name }}
</template>
<template #row-details="{ item }">
<pre>{{ item }}</pre>
</template>
</b-table>
</div>
I have setup my table in admin side of our application with MDBReact using the datatable. This table shows some small details of the stories that I have.
Now I have to make a row clickable i.e. add onClick to make a function call with the story id passed as an argument to this function.
Question:
How do I add onClick event to the datatable row?
(Below is my code.)
class Posts extends Component {
componentDidMount() {
this.getPosts();
}
getPosts = async () => {
const response = await fetch("http://****************/get_posts");
const post_items = await response.json();
this.setState({ posts: post_items.result }, () => {
console.log(this.state.posts);
this.setState({ tableRows: this.assemblePost() });
});
};
assemblePost = () => {
let posts = this.state.posts.map((post) => {
let mongoDate = post.dateAdded.toString();
let mainDate = JSON.stringify(new Date(mongoDate));
return {
postTitle: post.storyTitle,
// postDescription: post.storyDescription,
dateAdded: mainDate.slice(1, 11),
thankedBy: post.thankedBy.length,
reportedBy: post.reportedBy ? post.reportedBy.length : "",
userEmail: post.userEmail[0],
categoryName: post.categoryName[0],
};
});
console.log(posts);
return posts;
};
state = {
posts: [],
tableRows: [],
};
render() {
const data = {
columns: [
{
label: "Story Title",
field: "postTitle",
},
{ label: "Category Name", field: "categoryName" },
{
label: "User Email",
field: "userEmail",
},
{
label: "Date Added",
field: "dateAdded",
},
{
label: "Thanked",
field: "thankedBy",
},
{
label: "Reported",
field: "reportedBy",
},
],
rows: this.state.tableRows,
};
return (
<div className="MDBtable">
<p className="posts">Posts List</p>
<MDBDataTable striped bordered hover data={data} />
</div>
);
}
}
export default Posts;
To pull this off, here's what I did, but you'll need to appreciate these:
MDBDataTable requires you to manually define the columns and rows.
For data to render seamlessly, you define columns.field that correspond to rows[key]
Now, here's the logic, if you define a rows[key] that does not correspond to any columns.field, then that rows[key] is defined for an entire row just like we often pass index when working with map().
So based on the above observations,you can just pass the click event as a key/value pair to the row.And it will work just fine.
// ...
assemblePost = () => {
let posts = this.state.posts.map(
(post, i) => {
let mongoDate = post.dateAdded.toString();
let mainDate = JSON.stringify(new Date(mongoDate));
return {
index: i + 1, // advisable to pass a unique identifier per item/row
clickEvent: () => this.handleClick(storyId), // pass it a callback function
postTitle: post.storyTitle,
// ...others
categoryName: post.categoryName[0],
};
});
console.log(posts);
return posts;
};
// ...
Notice this clickEvent: () => this.handleClick(storyId), will be attached to the entire row.
var vue_app = new Vue({
el: '#id1',
data: {
v1:[],
},
methods:{
pushUnique: function() {
this.v1.push({'id':1,'name':'josh'});
this.v1.push({'id':1,'name':'josh'}); //this should not work.
},
},
});
In above code the second push should not execute. I would like to keep id unique. How can this be done in Vue.
THanks
I would move to storing data in an object (keyed by id) and use a computed property to produce your v1 array. For example
data: {
v1obj: {}
},
computed: {
v1 () {
return Object.keys(this.v1obj).map(id => ({ id, name: this.v1obj[id] }))
}
}
Then you can use methods like Object.prototype.hasOwnProperty() to check for existing keys...
methods: {
pushUnique () {
let id = 1
let name = 'josh'
if (!this.v1obj.hasOwnProperty(id)) {
this.v1obj[id] = name
}
}
}
I'm developing a vue-based system, and for my tables, I'm using this: https://github.com/matfish2/vue-tables-2
The component has lots of options. There's the possibility to define custom cell templates to manipulate data before showing them, like this one for the "created_at" column:
templates: {
created_at(h, row) {
return this.formatDate(row.created_at);
},
//(...)
}
I can add other templates as well:
var user_id = function(h,row){return row.id};
...
templates: {
created_at(h, row) {
return this.formatDate(row.created_at);
},
user_id,
(...) // etc
}
But I'd like to group the functions into a variable, so I could reuse the code, like:
var custom_template = [
{ user_id(h,row){return row.id} },
{ user_name(h,row){return row.name} },
{ created_at(h, row){return this.formatDate(row.created_at)} }
];
templates: {
custom_templates
}
EDIT 1:
So i could have something like:
templates: {
user_id(h,row){return row.id} ,
user_name(h,row){return row.name} ,
created_at(h, row){return this.formatDate(row.created_at)}
}
It's not working, but is it possible? Do I need extra code to achieve this?
Thanks! =)
var custom_template = [
function user_id(h, row) {
return row.id;
},
function user_name(h, row) {
return row.name;
},
function created_at(h, row) {
return row.created_at;
}
];
custom_template.forEach(
item => console.log(item(1, {
id: 1,
name: "test",
created_at: new Date()
})));
You need to combine Igor's solution to create array of functions with some form of expanding the array, such as the spread syntax
I have an array, that I need to populate with multiple 'properties', something like this:
[
{
name: 'Date',
value: '27 Oct'
},
{
name: 'Type',
value: 'Image'
},
{
name: 'Client',
value: 'Apple'
}
]
I want to list out all the properties in ul and I want to have a + button, that will add a new object to an array (initially name and value will be blank). After double clicking on each item (li in this case), I want to make these properties editable.
So I did something like this:
var Properties = {};
Properties.items = [
{
name: m.prop('Date'),
value: m.prop('21 Dec'),
editable: m.prop(false)
}
];
This is my initial array.
Then, I have a controller:
Properties.controller = function() {
this.addNewItem = function() {
Properties.items.push({
name: m.prop('Sample'),
value: m.prop('Test'),
editable: m.prop(false)
})
};
this.toggleEditable = function(item) {
item.editable(!item.editable());
console.log(item.editable());
};
};
My view:
Properties.view = function(ctrl) {
return m('ul.list-unstyled', [
Properties.items.map(function(item) {
return m('li', [
item.editable() ? m('input', {
value: item.name(),
onkeyup: function(e) {
if(e.keyCode == 13) {
ctrl.toggleEditable.bind(ctrl, item);
} else {
m.withAttr('value', item.name)
}
}
}) :
m('span', {
ondblclick: ctrl.toggleEditable.bind(ctrl, item)
}, item.name()),
m('span', ': '),
m('span', item.value())
]);
}),
m('div',{
onclick: ctrl.addNewItem.bind(ctrl)
}, '+')
]);
};
And finally:
m.mount(document.querySelector('.container'), Properties);
But when I start typing, it sort of overwrites what I wrote and doesn't save the new value..
Any ideas, anyone?
You should keep your original array as the data model, pass it to Properties and let Properties store only the editable state. That is a nice data separation and will simplify the code. It may fix the save problem as well, since it can just set the array value directly in the onkeyup event.
For the overwrite problem, maybe adding a key attribute will work? It seems related. Check out http://mithril.js.org/mithril.html#dealing-with-focus for more information.