Hi Everyone, I'm trying to achieve adding edit and delete button in ReactJS using Mui Datatable, but the problem is that it keeps on repeating because of the Map sorry I'm just new in ReactJS anyways, here is my image and my code:
This is an example of my image:
And This My Code:
import React, { Component } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
import MUIDataTable from "mui-datatables";
const Client = (props) => (
<>
<Link to={"client/edit/" + props.client._id} className="btn btn-primary">
Edit
</Link>
<a
href="client"
onClick={() => {
props.deleteClient(props.client._id);
}}
className="btn btn-danger"
>
Delete
</a>
</>
);
export default class ClientsList extends Component {
constructor(props) {
super(props);
this.deleteClient = this.deleteClient.bind(this);
this.state = { clients: [] };
}
componentDidMount() {
axios
.get("http://localhost:5000/clients/")
.then((response) => {
this.setState({ clients: response.data });
})
.catch((error) => {
console.log(error);
});
}
deleteClient(id) {
axios.delete("http://localhost:5000/clients/" + id).then((response) => {
console.log(response.data);
});
this.setState({
clients: this.state.clients.filter((el) => el._id !== id),
});
}
// This is the map I was talking about:
clientList() {
return this.state.clients.map((currentclient) => {
return (
<Client
client={currentclient}
deleteClient={this.deleteClient}
key={currentclient._id}
/>
);
});
}
render() {
const columns = [
{
name: "name",
label: "Name",
options: {
filter: true,
sort: true,
},
},
{
name: "address",
label: "Address",
options: {
filter: true,
sort: true,
},
},
{
name: "mobile",
label: "Mobile",
options: {
filter: true,
sort: true,
},
},
{
name: "email",
label: "Email",
options: {
filter: true,
sort: true,
},
},
{
name: "gender",
label: "Gender",
options: {
filter: true,
sort: true,
},
},
{
name: "birthday",
label: "Birthday",
options: {
filter: true,
sort: true,
},
},
{
name: "facebookPage",
label: "Facebook Page",
options: {
filter: true,
sort: true,
},
},
{
name: "facebookName",
label: "Facebook Name",
options: {
filter: true,
sort: true,
},
},
{
name: "existing",
label: "Existing",
options: {
filter: true,
sort: true,
},
},
{
name: "remarks",
label: "Remarks",
options: {
filter: true,
sort: true,
},
},
{
name: "Action",
options: {
customBodyRender: () => {
return <>{this.clientList()}</>;
},
},
},
];
const { clients } = this.state;
return (
<>
<br />
<br />
<br />
<div style={{ margin: "10px 15px", overflowX: "auto" }}>
<Link to={"client/create/"} className="btn btn-primary pull-right">
Add Client Data
</Link>
<br />
<br />
<br />
<MUIDataTable data={clients} columns={columns} />
</div>
</>
);
}
}
Thank you for your help and understanding I really appreciate it!
You can just not use map in the clientList() function because you are returning (edit, delete ) of all the clients for each row in the table. you also can pass the row data like I will show in the link on each button and have the _id as a hidden column on your table so that you can have access on it.
import React, { Component } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
import MUIDataTable from "mui-datatables";
const Client = (props) => (
<>
<Link to={"client/edit/" + props.client._id} className="btn btn-primary">
Edit
</Link>
<a
href="client"
onClick={() => {
props.deleteClient(props.client._id);
}}
className="btn btn-danger"
>
Delete
</a>
</>
);
export default class ClientsList extends Component {
constructor(props) {
super(props);
this.deleteClient = this.deleteClient.bind(this);
this.state = {
clients: [{
}]
};
}
componentDidMount() {
axios
.get("http://localhost:5000/clients/")
.then((response) => {
this.setState({ clients: response.data });
})
.catch((error) => {
console.log(error);
});
}
deleteClient(id) {
axios.delete("http://localhost:5000/clients/" + id).then((response) => {
console.log(response.data);
});
this.setState({
clients: this.state.clients.filter((el) => el._id !== id),
});
}
// This is the map I was talking about:
clientList(currentclient) {
// current cleint her is an array that contain all the columns values for the row specify
// assuming that _id will be the first column
return (
<Client
client={currentclient}
deleteClient={this.deleteClient}
key={currentclient[0]}
/>
);
}
render() {
const columns = [
{
name: "_id",
options: {
display: false,
}
},
{
name: "name",
label: "Name",
options: {
filter: true,
sort: true,
},
},
{
name: "address",
label: "Address",
options: {
filter: true,
sort: true,
},
},
{
name: "mobile",
label: "Mobile",
options: {
filter: true,
sort: true,
},
},
{
name: "email",
label: "Email",
options: {
filter: true,
sort: true,
},
},
{
name: "gender",
label: "Gender",
options: {
filter: true,
sort: true,
},
},
{
name: "birthday",
label: "Birthday",
options: {
filter: true,
sort: true,
},
},
{
name: "facebookPage",
label: "Facebook Page",
options: {
filter: true,
sort: true,
},
},
{
name: "facebookName",
label: "Facebook Name",
options: {
filter: true,
sort: true,
},
},
{
name: "existing",
label: "Existing",
options: {
filter: true,
sort: true,
},
},
{
name: "remarks",
label: "Remarks",
options: {
filter: true,
sort: true,
},
},
{
name: "Action",
options: {
customBodyRender: (value, tableMeta, updateValue) => {
return <>{this.clientList(tableMeta.rowData)}</>;
},
},
},
];
const { clients } = this.state;
return (
<>
<br />
<br />
<br />
<div style={{ margin: "10px 15px", overflowX: "auto" }}>
<Link to={"client/create/"} className="btn btn-primary pull-right">
Add Client Data
</Link>
<br />
<br />
<br />
<MUIDataTable data={clients} columns={columns} />
</div>
</>
);
}
}
you can deconstruct clients data from state and then pass it to MUIDataTable
const { clients } = this.state;
const rows = clients.map((client) => {
return {
// assuming atributes
name: client.name,
address: client.address,
mobile: client.mobile,
email: client.email,
gender: client.gender,
birthday: client.birthday,
action: <Link to=`client/edit/${client.id}` calssName='btn btn-primary'> Edit </Link> <a href='client' onClick={() => this.deleteClient(client.id)}> delete </a>
}
}
and then pass it in data props in MUIDataTable
<MUIDataTable data={rows} columns={columns} />
this is an example of a working snippet, tweak it to match you need
const rows = orders.map((order) => {
return {
ref: <Link to={"/orders/" + order.ref}>{order.ref.slice(0, 8)}</Link>,
amount: order.amount,
donated: order.ticketsDetails[0].ticketDonate != "" ? "Yes" : "No",
date: order.createdAt.slice(0, 16),
};
});
const columns = [
{
label: "Ref",
name: "ref",
options: {
filter: true,
sort: true,
},
},
{
label: "Amount",
name: "amount",
options: {
filter: true,
sort: true,
},
},
{
label: "Date",
name: "date",
options: {
filter: true,
sort: true,
},
},
{
label: "Donated",
name: "donated",
options: {
filter: true,
sort: true,
},
},
];
return (
<div className="orders-container">
<MUIDataTable columns={columns} data={rows} />
</div>
);
Related
I have a filter dropdown in the header and when users apply the filters I want to run the assigned function according to their choices but I couldn't run it, I got "... is not a function" error.
Also, even if I solve this problem, I will probably have problems in applying more than one filter at the same time, I would be very grateful if anyone could offer a solution in the form of applying filters in multiple ways.
Example Object:
const filters = [
{
section: 'Call Types',
slug: 'call-types',
type: 'multiselect',
options: [
{
label: 'Inbound',
slug: 'inbound',
value: true,
func: (data) =>
data.items.filter((item) => {
return item.className === 'ib'
}),
},
{
label: 'Outbound',
slug: 'outbound',
value: true,
func: (data) =>
data.items.filter((item) => {
return item.className === 'ob'
}),
},
],
},
{
section: 'Data Types',
slug: 'data-types',
type: 'multiselect',
options: [
{
label: 'Sentiment',
slug: 'sentiment',
value: false,
func: (data) =>
data.items.filter((item) => {
return item.d1 === 1
}),
},
{
label: 'Capture Fails',
slug: 'capture-fails',
value: false,
func: (data) =>
data.items.filter((item) => {
return item.d2 > 0.5
}),
},
{
label: 'Audio Notes',
slug: 'audio-notes',
value: false,
func: (data) =>
data.items.filter((item) => {
return item.d1 === 1 && item.d2 > 0.5
}),
},
],
},
{
section: 'Others',
slug: 'others',
type: 'singleselect',
options: [
{
label: 'Show All Agents',
slug: 'show-all-agents',
value: true,
func: (data) =>
data.items.filter((item) => {
return item
}),
},
],
},
]
exports.filters = filters
And here is the method I'm trying to run when users click to "Filter" button.
Timeline vue file:
<template>
<vis-timeline
:groups="filteredData.groups"
:items="filteredData.items"
/>
</template>
<script>
import { mapActions, mapState } from 'vuex'
import VisTimeline from '~/components/UI/VisTimeline.vue'
export default {
components: { VisTimeline },
computed: {
...mapState({
callTimeline: (state) => state.agents.callTimeline,
timelineLoading: (state) => state.agents.loading,
filterOption: (state) => state.agents.filterOption,
}),
filteredData() {
var filtered = Object.assign(
{},
JSON.parse(JSON.stringify(this.callTimeline))
)
return this.filterItems(filtered)
},
},
methods: {
filterItems(data) {
if (this.filterOption) {
this.filterOption.forEach((filter) => {
filter.options.some((option) => {
if (option.value) {
return option.func(data)
}
})
})
}
return data
}
}
}
</script>
Header vue file:
<template>
<a-layout-header style="background: #fff; padding: 0">
<a-dropdown
:trigger="['click']"
:visible="visible"
#visibleChange="handleVisible"
>
<template #overlay>
<a-menu>
<a-menu-item-group
v-for="filter in filters"
:key="filter.slug"
:title="filter.section"
>
<a-menu-item :key="filter.slug">
<a-checkbox
v-for="option in filter.options"
:key="option.slug"
:name="option.slug"
:checked="option.value"
#change="filterChanges"
>
{{ option.label }}
</a-checkbox>
</a-menu-item>
</a-menu-item-group>
<a-menu-divider />
<a-menu-item key="buttons"
><div class="flex justify-between">
<a-button #click="visible = false" type="danger" ghost
>Cancel</a-button
>
<a-button #click="applyFilters" type="primary">Filter</a-button>
</div></a-menu-item
>
</a-menu>
</template>
<div class="mb-1">
<a-button type="primary">
<div class="flex flex-row justify-center space-x-2 items-center">
<a-icon type="filter" />
<span>Filters</span>
<a-icon type="down" />
</div>
</a-button>
</div>
</a-dropdown>
</a-layout-header>
</template>
<script>
import { mapActions} from 'vuex'
import { filters } from '~/utils/Filters'
export default {
name: 'DBHeader',
data() {
return {
visible: false,
filters: filters,
}
},
methods: {
...mapActions({
setFilterOption: 'agents/setFilterOption',
}),
handleVisible(flag) {
this.visible = flag
},
filterChanges(e) {
this.filters.forEach((filter) => {
filter.options.some((option) => {
if (option.slug === e.target.name) {
option.value = e.target.checked
}
})
})
},
applyFilters() {
var filters = Object.assign([], JSON.parse(JSON.stringify(this.filters)))
this.setFilterOption(filters)
this.visible = false
},
},
}
</script>
I'm trying to achieve adding edit and delete button via MUI Datatable it's already applied but whenever I pressed the edit button the URL says the undefined. Here is an image and my code. Thanks for your help really appreciate it.
as you can see it says /client/edit/undefined whereas should be an id
Code:
import React, { Component } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
import MUIDataTable from "mui-datatables";
const Client = (props) => (
<>
<Link to={"client/edit/" + props.client._id} className="btn btn-primary">
Edit
</Link>
<a
href="client"
onClick={() => {
props.deleteClient(props.client._id);
}}
className="btn btn-danger"
>
Delete
</a>
</>
);
export default class ClientsList extends Component {
constructor(props) {
super(props);
this.deleteClient = this.deleteClient.bind(this);
this.state = { clients: [] };
}
componentDidMount() {
axios
.get("http://localhost:5000/clients/")
.then((response) => {
this.setState({ clients: response.data });
})
.catch((error) => {
console.log(error);
});
}
deleteClient(id) {
axios.delete("http://localhost:5000/clients/" + id).then((response) => {
console.log(response.data);
});
this.setState({
clients: this.state.clients.filter((el) => el._id !== id),
});
}
clientList(currentclient) {
return (
<Client
client={currentclient}
deleteClient={this.deleteClient}
key={currentclient[0]}
/>
);
}
render() {
const columns = [
{
name: "_id",
options: {
display: false,
},
},
{
name: "name",
label: "Name",
options: {
filter: true,
sort: true,
},
},
{
name: "address",
label: "Address",
options: {
filter: true,
sort: true,
},
},
{
name: "mobile",
label: "Mobile",
options: {
filter: true,
sort: true,
},
},
{
name: "email",
label: "Email",
options: {
filter: true,
sort: true,
},
},
{
name: "gender",
label: "Gender",
options: {
filter: true,
sort: true,
},
},
{
name: "birthday",
label: "Birthday",
options: {
filter: true,
sort: true,
},
},
{
name: "facebookPage",
label: "Facebook Page",
options: {
filter: true,
sort: true,
},
},
{
name: "facebookName",
label: "Facebook Name",
options: {
filter: true,
sort: true,
},
},
{
name: "existing",
label: "Existing",
options: {
filter: true,
sort: true,
},
},
{
name: "remarks",
label: "Remarks",
options: {
filter: true,
sort: true,
},
},
{
name: "Action",
options: {
customBodyRender: (value, tableMeta, updateValue) => {
return <>{this.clientList(tableMeta.rowData)}</>;
},
},
},
];
const { clients } = this.state;
return (
<>
<br />
<br />
<br />
<div style={{ margin: "10px 15px", overflowX: "auto" }}>
<Link to={"client/create/"} className="btn btn-primary pull-right">
Add Client Data
</Link>
<br />
<br />
<br />
<MUIDataTable data={clients} columns={columns} />
</div>
</>
);
}
}
Everyone, the Table data is transposed into an array, this is my code if you wanted to have a edit and delete button using mui datatables.
import React, { Component } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
import MUIDataTable from "mui-datatables";
const Client = (props) => (
<>
{console.log("Client Props", props)}
<Link to={"client/edit/" + props.client[0]} className="btn btn-primary">
Edit
</Link>
<a
href="client"
onClick={() => {
props.deleteClient(props.client[0]);
}}
className="btn btn-danger"
>
Delete
</a>
</>
);
export default class ClientsList extends Component {
constructor(props) {
super(props);
this.deleteClient = this.deleteClient.bind(this);
this.state = { clients: [] };
}
componentDidMount() {
axios
.get("http://localhost:5000/clients/")
.then((response) => {
this.setState({ clients: response.data });
})
.catch((error) => {
console.log(error);
});
}
deleteClient(id) {
axios.delete("http://localhost:5000/clients/" + id).then((response) => {
console.log(response.data);
});
this.setState({
clients: this.state.clients.filter((el) => el._id !== id),
});
}
clientList(currentclient) {
return (
<Client
client={currentclient}
deleteClient={this.deleteClient}
key={currentclient[0]}
/>
);
}
render() {
const columns = [
{
name: "_id",
options: {
display: false,
},
},
{
name: "name",
label: "Name",
options: {
filter: true,
sort: true,
},
},
{
name: "address",
label: "Address",
options: {
filter: true,
sort: true,
},
},
{
name: "mobile",
label: "Mobile",
options: {
filter: true,
sort: true,
},
},
{
name: "email",
label: "Email",
options: {
filter: true,
sort: true,
},
},
{
name: "gender",
label: "Gender",
options: {
filter: true,
sort: true,
},
},
{
name: "birthday",
label: "Birthday",
options: {
filter: true,
sort: true,
},
},
{
name: "facebookPage",
label: "Facebook Page",
options: {
filter: true,
sort: true,
},
},
{
name: "facebookName",
label: "Facebook Name",
options: {
filter: true,
sort: true,
},
},
{
name: "existing",
label: "Existing",
options: {
filter: true,
sort: true,
},
},
{
name: "remarks",
label: "Remarks",
options: {
filter: true,
sort: true,
},
},
{
name: "Action",
options: {
customBodyRender: (value, tableMeta, updateValue) => {
return <>{this.clientList(tableMeta.rowData)}</>;
},
},
},
];
const { clients } = this.state;
return (
<>
<br />
<br />
<br />
<div style={{ margin: "10px 15px", overflowX: "auto" }}>
<Link to={"client/create/"} className="btn btn-primary pull-right">
Add Client Data
</Link>
<br />
<br />
<br />
<MUIDataTable data={clients} columns={columns} />
</div>
</>
);
}
}
I'm new on ReactJS and this is my first page that I created, but I'm having some problems with set variables.
What I need is fill the variable table.data with the values that comes from const response = await api.get('/users') and render the table with this values when page loads.
I have the following code:
import React, { useState, useEffect } from 'react';
import { Fade } from "#material-ui/core";
import MaterialTable from 'material-table';
import { makeStyles } from '#material-ui/core/styles';
import api from '../../services/api.js';
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
width: '70%',
margin: 'auto',
marginTop: 20,
boxShadow: '0px 0px 8px 0px rgba(0,0,0,0.4)'
}
}));
function User(props) {
const classes = useStyles();
const [checked, setChecked] = useState(false);
let table = {
data: [
{ name: "Patrick Mahomes", sector: "Quaterback", email: "patrick#nfl.com", tel: "1234" },
{ name: "Tom Brady", sector: "Quaterback", email: "tom#nfl.com", tel: "5678" },
{ name: "Julio Jones", sector: "Wide Receiver", email: "julio#nfl.com", tel: "9876" }
]
}
let config = {
columns: [
{ title: 'Name', field: 'name' },
{ title: 'Sector', field: 'sector' },
{ title: 'E-mail', field: 'email'},
{ title: 'Tel', field: 'tel'}
],
actions: [
{ icon: 'create', tooltip: 'Edit', onClick: (rowData) => alert('Edit')},
{ icon: 'lock', tooltip: 'Block', onClick: (rowData) => alert('Block')},
{ icon: 'delete', tooltip: 'Delete', onClick: (rowData) => alert('Delete')},
{ icon: 'visibility', tooltip: 'Access', onClick: (rowData) => alert('Access')},
{ icon: "add_box", tooltip: "Add", position: "toolbar", onClick: () => { alert('Add') } }
],
options: {
headerStyle: { color: 'rgba(0, 0, 0, 0.54)' },
actionsColumnIndex: -1,
exportButton: true,
paging: true,
pageSize: 10,
pageSizeOptions: [],
paginationType: 'normal'
},
localization: {
body: {
emptyDataSourceMessage: 'No data'
},
toolbar: {
searchTooltip: 'Search',
searchPlaceholder: 'Search',
exportTitle: 'Export'
},
pagination: {
labelRowsSelect: 'Lines',
labelDisplayedRows: '{from} to {to} for {count} itens',
firstTooltip: 'First',
previousTooltip: 'Previous',
nextTooltip: 'Next',
lastTooltip: 'Last'
},
header: {
actions: 'Actions'
}
}
}
useEffect(() => {
setChecked(prev => !prev);
async function loadUsers() {
const response = await api.get('/users');
table.data = response.data;
}
loadUsers();
}, [])
return (
<>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<Fade in={checked} style={{ transitionDelay: checked ? '300ms' : '0ms' }}>
<div className={classes.root}>
<MaterialTable editable={config.editable} options={config.options} localization={config.localization} title="Usuários" columns={config.columns} data={table.data} actions={config.actions}></MaterialTable>
</div>
</Fade>
</>
);
}
export default User;
The previous example will show 3 users that I fixed on variable table.data with 4 columns (name, sector, email, tel).
In a functional component, each render is really a new function call. So any variables you declare inside the component and destroyed and re-created. This means that table is set back to your initial value each render. Even if your useEffect is setting it correctly after the first render, it will just be reset on the next.
This is what state is for: to keep track of variables between renders. Replace your let table, with a new state hook.
const [table, setTable] = useState({
data: [
{ name: "Patrick Mahomes", sector: "Quaterback", email: "patrick#nfl.com", tel: "1234" },
{ name: "Tom Brady", sector: "Quaterback", email: "tom#nfl.com", tel: "5678" },
{ name: "Julio Jones", sector: "Wide Receiver", email: "julio#nfl.com", tel: "9876" }
]
});
Then use it like this:
useEffect(() => {
setChecked(prev => !prev);
async function loadUsers() {
const response = await api.get('/users');
setTable(prev => ({...prev, data: response.data});
}
loadUsers();
}, [])
Since table.data is not a state variable, it is regenerated as it was declared originally every time the component renders, meaning that by the time it arrives as a prop to your component it will always be the same value (when you change the value of table.data in useEffect it is too late). You need to change table.data to a state variable, and then in your useEffect hook you can update the value of table.data to the value of response.data. This will cause the component to be re-rendered but with the updated value.
Here's an example of how you might do that:
import React, { useState, useEffect } from 'react';
import { Fade } from "#material-ui/core";
import MaterialTable from 'material-table';
import { makeStyles } from '#material-ui/core/styles';
import api from '../../services/api.js';
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
width: '70%',
margin: 'auto',
marginTop: 20,
boxShadow: '0px 0px 8px 0px rgba(0,0,0,0.4)'
}
}));
function User(props) {
const classes = useStyles();
const [checked, setChecked] = useState(false);
const [tableData, setTableData] = useState([]);
let config = {
columns: [
{ title: 'Name', field: 'name' },
{ title: 'Sector', field: 'sector' },
{ title: 'E-mail', field: 'email'},
{ title: 'Tel', field: 'tel'}
],
actions: [
{ icon: 'create', tooltip: 'Edit', onClick: (rowData) => alert('Edit')},
{ icon: 'lock', tooltip: 'Block', onClick: (rowData) => alert('Block')},
{ icon: 'delete', tooltip: 'Delete', onClick: (rowData) => alert('Delete')},
{ icon: 'visibility', tooltip: 'Access', onClick: (rowData) => alert('Access')},
{ icon: "add_box", tooltip: "Add", position: "toolbar", onClick: () => { alert('Add') } }
],
options: {
headerStyle: { color: 'rgba(0, 0, 0, 0.54)' },
actionsColumnIndex: -1,
exportButton: true,
paging: true,
pageSize: 10,
pageSizeOptions: [],
paginationType: 'normal'
},
localization: {
body: {
emptyDataSourceMessage: 'No data'
},
toolbar: {
searchTooltip: 'Search',
searchPlaceholder: 'Search',
exportTitle: 'Export'
},
pagination: {
labelRowsSelect: 'Lines',
labelDisplayedRows: '{from} to {to} for {count} itens',
firstTooltip: 'First',
previousTooltip: 'Previous',
nextTooltip: 'Next',
lastTooltip: 'Last'
},
header: {
actions: 'Actions'
}
}
}
useEffect(() => {
setChecked(prev => !prev);
async function loadUsers() {
const response = await api.get('/users');
setTableData(response.data);
}
loadUsers();
}, [])
return (
<>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<Fade in={checked} style={{ transitionDelay: checked ? '300ms' : '0ms' }}>
<div className={classes.root}>
<MaterialTable editable={config.editable} options={config.options} localization={config.localization} title="Usuários" columns={config.columns} data={tableData} actions={config.actions}></MaterialTable>
</div>
</Fade>
</>
);
}
export default User;
I'm making a dynamic admin panel with crud generator with the help of laravel and vue.
I have a table where I'm loading data asynchronously from API. There is an is_featured column in my table which I want to be a switch. So that the user can change the value from the table page instead of going to edit page.
To generate my entire table there is a configuration object that contains which fields to show and the type of that field and other metadata. In the configuration object, there is a field named prerender which is responsible to prerender fields that require calling other API or some editable fields like select dropdown or switch.
To make switch and select fields work, I have an empty object named fieldData.
When a field with type: boolean and prerender: true is found, my code will initialize a property with field.name as property name in field data and fill it with the corresponding values under that field name
this.fieldData[field.name] = {};
this.tableData.forEach(
data => (this.fieldData[field.name][data.id] = data[field.name])
);
BUT THE SWITCH AND SELECT ARE NOT WORKING HERE
So I need help.
Here's my entire code for reference
<template>
<div class="app-container">
<el-row :gutter="20" style="display: flex; align-items: center;">
<el-col :span="10">
<h1 style="text-transform: capatilize;">{{ resourceName }}</h1>
</el-col>
<el-col :span="14" style="display: flex; justify-content: flex-end; align-items: center">
<el-input
v-model="navigation.search"
placeholder="Search anything here"
prefix-icon="el-icon-search"
style="width: 300px; margin: 0 10px;"
#keydown.enter.native="handleGlobalSearch"
/>
<FilterPannel
style="margin: 0 10px"
:filter-pannel-obj="filterPannelObj"
#set-filter="handleFilterVals"
#reset-filter="getTableData({})"
/>
<Import
:url="`/api/${resourceName}/upload`"
#import-success="handleImportSucces"
#import-error="handleImportError"
/>
<Export :url="`/api/${resourceName}/export`" :selected-ids="selected.map(el => el.id)" />
<el-button
type="info"
icon="el-icon-delete"
#click="$refs['table'].clearSelection()"
>Clear Selection</el-button>
<el-button type="danger" icon="el-icon-delete" #click="handleMultipleDelete">Delete Selected</el-button>
</el-col>
</el-row>
<el-row>
<el-table
ref="table"
v-loading="loading.tableData"
:data="tableData"
border
:row-key="getRowKeys"
#sort-change="handleSortChange"
#selection-change="handleSelectionChange"
>
<el-table-column type="selection" label="Selection" reserve-selection />
<el-table-column label="Actions" width="200">
<template slot-scope="scope">
<div style="display: flex; justify-content: space-around;">
<el-button
icon="el-icon-view"
type="primary"
circle
#click="$router.push(`/${resourceName}/view/${scope.row.id}`)"
/>
<el-button
icon="el-icon-edit"
type="success"
circle
#click="$router.push(`/${resourceName}/edit/${scope.row.id}`)"
/>
<el-button
icon="el-icon-delete"
type="danger"
circle
#click="handleDeleteClick(scope.row.id)"
/>
</div>
</template>
</el-table-column>
<el-table-column
v-for="field in fieldsToShow"
:key="field.name"
:prop="field.name"
:label="field.name.replace('_',' ')"
sortable="custom"
>
<template slot-scope="scope">
<div
v-if="field.type=='multilangtext'"
class="cell"
>{{ JSON.parse(scope.row[field.name])[$store.state.app.language] }}</div>
<div v-if="field.type=='text'" class="cell">{{ scope.row[field.name] }}</div>
<el-tag v-if="field.type=='tag'">{{ scope.row.type }}</el-tag>
<img v-if="field.type=='image'" :src="scope.row.icon" width="100px" height="100px" />
<div v-if="field.type=='oneFrom'" class="cell">
<el-tag
:key="scope.row[field.name]"
>{{field.multilang ? scope.row[field.name][$store.state.app.language] : scope.row[field.name]}}</el-tag>
</div>
<div v-if="field.type=='manyFrom'" class="cell">
<el-tag
style="margin: 5px"
v-for="(item, index) in scope.row[field.name]"
:key="`${field.multilang ? item[$store.state.app.language] : item}+${index}`"
>{{field.multilang ? item[$store.state.app.language] : item}}</el-tag>
</div>
<div v-if="field.type=='boolean'" class="cell">
{{scope.row.id}} =>
{{field.name}} =>>
{{scope.row[field.name]}} =>>>
{{fieldData[field.name][scope.row.id]}}
<el-switch
:key="`switch${scope.row.id}`"
v-model="fieldData[field.name][scope.row.id]"
:active-value="1"
:inactive-value="0"
></el-switch>
</div>
<div v-if="field.type=='select'" class="cell">
<el-select :key="`select${scope.row.id}`" v-model="fieldData[field.name][scope.row.id]" placeholder="Select">
<el-option
v-for="item in field.options"
:key="item"
:label="item"
:value="item"
></el-option>
</el-select>
</div>
</template>
</el-table-column>
</el-table>
</el-row>
<el-row>
<pagination
style="padding: 0;"
:total="paginationData.total"
:page.sync="paginationData.current_page"
:limit.sync="paginationData.per_page"
#pagination="handlePagination"
/>
</el-row>
</div>
</template>
<script>
import Pagination from '#/components/Pagination';
import FilterPannel from '#/components/FilterPannel';
import Export from './components/Export'; // ? not needed
import Import from './components/Import';
import axios from 'axios';
import Resource from '#/api/resource';
const resourceName = 'coupons';
const ResourceApi = new Resource(resourceName);
export default {
name: 'CategoryList',
components: {
Pagination,
FilterPannel,
Export,
Import,
},
data() {
return {
resourceName: resourceName,
language: 'en',
tableData: [],
fieldData: {},
fieldsToShow: [
{ name: 'title', type: 'multilangtext' },
// { name: 'description', type: 'multilangtext' },
{ name: 'code', type: 'text' },
// { name: 'expiry_date', type: 'text' },
{ name: 'type', type: 'tag' },
{
name: 'store_id',
type: 'oneFrom',
url: '/api/stores/',
foreignKey: 'store_id',
attrName: 'name',
multilang: true,
prerender: true,
},
{
name: 'tags',
type: 'manyFrom',
url: '/api/tags?idsarr=',
foreignKey: 'tags',
attrName: 'name',
multilang: true,
prerender: true,
},
{
name: 'brands',
type: 'manyFrom',
url: '/api/brands?idsarr=',
foreignKey: 'brands',
attrName: 'name',
multilang: true,
prerender: true,
},
// { name: 'brands', type: 'text' },
{ name: 'is_featured', type: 'boolean', prerender: true },
{
name: 'status',
type: 'select',
options: ['publish', 'draft', 'trash'],
prerender: true,
},
],
paginationData: {
current_page: 0,
last_page: 0,
per_page: 0,
total: 0,
},
navigation: {
page: 1,
limit: 10,
sort: '',
'sort-order': 'asc',
filters: '',
search: '',
},
filters: '',
selected: [], // ? for selection
loading: {
tableData: false,
},
allData: [],
filterPannelObj: {
title: {
default: '',
type: 'Input',
label: 'Title',
},
description: {
default: '',
type: 'Input',
label: 'Description',
},
promo_text: {
default: '',
type: 'Input',
label: 'Promo Text',
},
type: {
default: [],
type: 'checkbox',
label: 'Type',
src: [
{ value: 'coupon', label: 'Coupon' },
{ value: 'offer', label: 'Offer' },
],
},
code: {
default: '',
type: 'Input',
label: 'Code',
},
store_id: {
default: [],
type: 'select',
label: 'Store',
src: [],
multiple: true,
},
brands: {
default: [],
type: 'select',
label: 'Brands',
src: [],
multiple: true,
},
tags: {
default: [],
type: 'select',
label: 'Tags',
src: [],
multiple: true,
},
cats: {
default: [],
type: 'select',
label: 'Categories',
src: [],
multiple: true,
},
},
};
},
watch: {
async 'navigation.search'(newVal, oldVal) {
await this.handleGlobalSearch();
},
},
async created() {
await this.getTableData({});
this.allData = await ResourceApi.list({ limit: -1 });
// to fill filter dialog selects
// To get brands
this.filterPannelObj.brands.src = (await axios.get(
`/api/brands?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
// To get tags
this.filterPannelObj.tags.src = (await axios.get(
`/api/tags?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
// To get categories
this.filterPannelObj.cats.src = (await axios.get(
`/api/categories?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
// To get stores
this.filterPannelObj.store_id.src = (await axios.get(
`/api/stores?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
},
methods: {
printScope(x) {
console.log('TCL: printScope -> x', x);
},
async getTableData(query) {
this.loading.tableData = true;
const responseData = await ResourceApi.list(query);
this.tableData = responseData.data;
this.paginationData = this.pick(
['current_page', 'last_page', 'per_page', 'total'],
responseData
);
Object.keys(this.paginationData).forEach(
key => (this.paginationData[key] = parseInt(this.paginationData[key]))
);
await this.handlePrerender();
this.loading.tableData = false;
},
async handlePrerender() {
this.fieldsToShow.forEach(async field => {
if (field.prerender) {
switch (field.type) {
case 'oneFrom': {
await this.setRelatedFieldName(field);
break;
}
case 'manyFrom': {
await this.setRelatedFieldName(field);
break;
}
case 'boolean': {
this.fieldData[field.name] = {};
this.tableData.forEach(
data => (this.fieldData[field.name][data.id] = data[field.name])
);
break;
}
case 'select': {
this.fieldData[field.name] = {};
this.tableData.forEach(
data => (this.fieldData[field.name][data.id] = data[field.name])
);
break;
}
}
}
});
},
// utils
pick(propsArr, srcObj) {
return Object.keys(srcObj).reduce((obj, k) => {
if (propsArr.includes(k)) {
obj[k] = srcObj[k];
}
return obj;
}, {});
},
// ? remember to refactor the parameter id
async setRelatedFieldName({
name,
type,
url,
foreignKey,
attrName,
multilang,
}) {
this.tableData.forEach(async data => {
if (type === 'oneFrom') {
data[name] = (await axios.get(`${url}${data[foreignKey]}`)).data[
attrName
];
if (multilang) {
data[name] = JSON.parse(data[name]);
}
} else if (type === 'manyFrom') {
data[name] = (await axios.get(`${url}${data[foreignKey]}`)).data.map(
idata => (multilang ? JSON.parse(idata[attrName]) : idata[attrName])
);
}
});
},
// Sort
async handleSortChange(change) {
this.navigation.sort = change.prop;
if (change.order === 'ascending') {
this.navigation['sort-order'] = 'asc';
} else if (change.order === 'descending') {
this.navigation['sort-order'] = 'desc';
}
await this.getTableData(this.navigation);
},
// Pagination
async handlePagination(obj) {
// obj page obj containing {page: ..., limit: ...}
this.navigation.page = obj.page;
this.navigation.limit = obj.limit;
await this.getTableData(this.navigation);
},
// Global Search
async handleGlobalSearch() {
await this.getTableData(this.navigation);
},
// ? Skipped for now
// Filters
async handleFilterVals(filterparams) {
console.log('TCL: handleFilterVals -> filterparams', filterparams);
this.navigation.filters = JSON.stringify(filterparams.filters);
this.navigation.sort = filterparams.sort.field;
this.navigation.sort = 'name';
this.navigation['sort-order'] = filterparams.sort.asc ? 'asc' : 'desc';
await this.getTableData(this.navigation);
},
async handleDeleteClick(id) {
ResourceApi.destroy(id)
.then(res => {
this.$message.success('Delete Successfully');
this.getTableData({ page: this.paginationData.current_page });
})
.error(err => {
this.$message.error(err);
});
},
// Selection methods
handleSelectionChange(selection) {
this.selected = selection;
},
getRowKeys(row) {
return row.id;
},
// Multiple Delete
handleMultipleDelete() {
axios
.delete(
`/api/${this.resourceName}/delete-multiple?ids=${this.selected
.map(item => item.id)
.join(',')}`
)
.then(async () => {
this.$message.success('Records deleted successfully');
await this.getTableData({ page: this.paginationData.current_page });
if (this.tableData.length === 0) {
await this.getTableData({
page: this.paginationData.current_page,
});
}
this.$refs.table.clearSelection();
})
.catch();
},
// Import Events
handleImportSucces() {
this.$message.success('New Data Imported');
this.getTableData({});
},
handleImportError(err) {
this.$message.error('There were some errors. CHK console');
console.log(err);
},
},
};
</script>
<style lang="scss" scoped>
</style>
I guess this is a reactive issue. The value of dict and actually not affecting the feildData dict.
changes -
this.fieldData[field.name] = {};
replace this with
self.$set(self.fieldData,field.name,{});
and
this.fieldData[field.name][data.id] = data[field.name] // to
//Replace
self.$set(self.fieldData[field.name],data.id, data[field.name]);
I think this will fix the issue.Instead of using = use $set for value assignment.
Codepen - https://codepen.io/Pratik__007/pen/gObGOKx
I'm trying to add a button to a cell in a react bootstrap table with the following is my code:
import React, { Component } from "react";
import BootstrapTable from "react-bootstrap-table-next";
import { Button } from 'reactstrap';
class ActionsCard extends Component {
constructor(props) {
super(props);
this.state = {
actions: [{action: "Upgrade device", details: "Upgrade device to version 0.1.1", _id: "1"}],
valid: true
};
this.columns = [
{
text: "Action",
dataField: "action",
sort: true,
editable: false,
headerStyle: (colum, colIndex) => {
return { width: "5%", textAlign: "left" };
}
},
{
text: "Details",
dataField: "details",
sort: true,
editable: false,
headerStyle: (colum, colIndex) => {
return { width: "12%", textAlign: "left" };
}
},
{
sort: true,
headerStyle: (colum, colIndex) => {
return { width: "16%", textAlign: "left" };
},
Header: 'Test',
Cell: cell => (
<Button onClick={() => console.log(cell.original)}>Upgrade</Button>
),
}
];
}
render() {
return (
<React.Fragment>
<BootstrapTable
keyField="_id"
data={this.state.actions}
columns={this.columns}
noDataIndication="No Interfaces available"
defaultSorted={[{ dataField: "action", order: "asc" }]}
/>
</React.Fragment>
);
}
}
export default ActionsCard;
However, when I run the code, the two first columns of the table appear as expected, but the third column is simply empty.
You can use formatter to add button as in this discussion mention
{
dataField: "databasePkey",
text: "Remove",
formatter: (cellContent: string, row: IMyColumnDefinition) => {
if (row.canRemove)
return <button className="btn btn-danger btn-xs" onClick={() => this.handleDelete(row.databasePkey)}>Delete</button>
return null
},
},