Related
I have a bootstrap table that shows a list of appliances. I am importing my data with Axios and for this specific table I am outputting data from two database tables, so I have one object which is called applianceReferences which stores another object called activeAppliances.
Not sure if it is relevant for this question, but just so you know.
Before talking about the problem, let me just post the whole code and below I will talk about the section that is giving me issues.
<template>
<b-container class="my-2">
<b-card v-if="showTable" class="ml-4 mr-4">
<b-table
search-placeholder="search"
:filter-included-fields="fields.map(f => f.key)"
include-filter
:items="applianceReferences"
:fields="fields"
/>
</b-card>
</b-container>
</template>
<script>
import {applianceService} from "#/services/appliance";
import CommonCollapsible from "#/components/common/CommonCollapsible";
import moment from 'moment';
export default {
components: { CommonCollapsible, CommonTable },
props: {
ownerId: String,
ownerType: String,
showDocuments: Boolean,
goToAppliances: "",
importAppliances: ""
},
data() {
return {
applianceReferences: [],
showTable: true
}
},
computed: {
fields() {
return [
{
key: 'referenceName',
label: this.$t('referenceName'),
sortable: true
},
{
key: 'activeAppliance.type',
label: this.$t('type'),
sortable: true,
},
{
key: 'activeAppliance.brandName',
label: this.$t('brand'),
sortable: true
},
{
key: 'activeAppliance.purchaseDate',
label: this.$t('purchaseDate'),
sortable: true,
template: {type: 'date', format: 'L'}
},
{
key: 'activeAppliance.warrantyDuration',
label: this.$t('warrantyDuration'),
sortable: true,
formatter: (warrantyDuration, applianceId, appliance) =>
this.$n(warrantyDuration) + ' ' +
this.$t(appliance.activeAppliance.warrantyDurationType ?
`model.appliance.warrantyDurationTypes.${appliance.activeAppliance.warrantyDurationType}` :
''
).toLocaleLowerCase(this.$i18n.locale),
sortByFormatted: (warrantyDuration, applianceId, appliance) =>
appliance.activeAppliance.warrantyDurationType === 'YEARS' ? warrantyDuration * 12 : warrantyDuration
},
{
key: 'activeAppliance.purchaseAmount',
label: this.$t('amount'),
sortable: true,
template: {
type: 'number', format: {minimumFractionDigits: '2', maximumFractionDigits: '2'},
foot: sum
}
},
{
key: 'actions',
template: {
type: 'actions',
head: [
{
text: 'overviewOfAppliances',
icon: 'fas fa-fw fa-arrow-right',
action: this.createAppliance
},
{
icon: 'fas fa-fw fa-file-excel',
action: this.importAppliance,
tooltip: this.$t('importAppliances'),
}
],
cell: [
{
icon: 'fa-trash',
variant: 'outline-danger',
action: this.remove
},
]
}
}
]
},
},
methods: {
load() {
Object.assign(this.$data, this.$options.data.apply(this));
this.applianceReferences = null;
applianceService.listApplianceReferences(this.ownerId).then(({data: applianceReferences}) => {
this.applianceReferences = applianceReferences;
this.applianceReferences.forEach( reference => {
applianceService.listAppliances(reference.id).then(result => {
this.$set(reference, 'appliances', result.data);
this.$set(reference, 'activeAppliance', result.data.find(appliance => appliance.active))
this.loaded = true
})
})
}).catch(error => {
console.error(error);
})
},
createAppliance(){
this.goToAppliances()
},
importAppliance(){
this.importAppliances()
},
},
watch: {
ownerId: {
immediate: true,
handler: 'load'
}
},
}
</script>
Okay, so the error occurs in this specific property:
{
key: 'activeAppliance.warrantyDuration',
label: this.$t('warrantyDuration'),
sortable: true,
formatter: (warrantyDuration, applianceId, appliance) =>
this.$n(warrantyDuration) + ' ' +
this.$t(appliance.activeAppliance.warrantyDurationType ?
`model.appliance.warrantyDurationTypes.${appliance.activeAppliance.warrantyDurationType}` :
''
).toLocaleLowerCase(this.$i18n.locale),
sortByFormatted: (warrantyDuration, applianceId, appliance) =>
appliance.activeAppliance.warrantyDurationType === 'YEARS' ? warrantyDuration * 12 : warrantyDuration
},
What I am basically doing here is combining two values from the object: warrantyDuration and warrantyDurationType and putting them in one single row in my bootstrap table.
The problem is that this is giving me an error: Cannot read properties of undefined (reading 'warrantyDurationType'
Yet the data actually outputs normally.
So what exactly does it want me to do?
I tried wrapping a v-if around the table to make sure that the application checks if the data exist before outputting it, but this does not solve the issue.
<div v-if="applianceReferences && applianceReferences.activeAppliance">
<b-card v-if="showTable" class="ml-4 mr-4">
<common-table
search-placeholder="search"
:filter-included-fields="fields.map(f => f.key)"
include-filter
:items="applianceReferences"
:fields="fields"
/>
</b-card>
</div>
Last, just to give you a full overview, my array looks like this:
Any ideas?
UPDATE: Solved by separating rendering into different functions and passing it the MenuItem:
renderItem(item: TreeMenuItem) {
console.log('render item:', item.label);
return (
<div>
<li>
{item.children ? <button onClick={this.toggleDisplayChildren}>{this.displayChildren ? 'v' : '>'}</button> : ''}
{this.showCheckbox ? <input type="checkbox" checked={item.selected} onClick={() => this.selectItem(item)} /> : ''}
{item.label}
</li>
{this.renderChildren(item)}
</div>
);
}
renderChildren(item: TreeMenuItem) {
return item.children && this.displayChildren
? item.children.map(child => {
child.parent = item;
return this.renderItem(child);
})
: '';
}
render() {
return <Host>{this.renderItem(this.item)}</Host>;
}
I'm having some difficulties figuring this out. I have an object with nested children and I am getting it rendered correctly. My issue lies in that I'm looking to add functionality so that when I select a parent, it will automatically extend all children elements as well(and grandchildren, etc). I am currently only able to do this for immediate children, but not any children of the children, etc.
I apologize for the verbose code. I tried to trim it down as much as possible while still showcasing the relevant parts. I am hoping to be able to achieve this without having a displayChild boolean property in the object itself, but rather use:
#Prop({ mutable: true }) displayChildren = false;
toggleDisplayChildren = () => {
this.displayChildren = !this.displayChildren;
};
My issue is that when I try to do it this way(in a forEach loop), the this keyword always refers to the parent calling the children, not the individual children.
TL;DR: When displayChildren = true for a parent element, I would like for every child element of that parent that also has children under it to also displayChildren = true.
Side note: I'm also having an issue where the checkbox is not updating automatically when item.selected is being set. It only updates when displayChildren is changed.
selectItem() {
this.item.selected = !this.item.selected;
this.isParent() ? this.selectParent() : this.selectChild();
this.item = { ...this.item };
}
private selectParent() {
if (!this.isParent()) return;
if (this.item.indeterminate) this.item.indeterminate = false;
if (!this.displayChildren) this.displayChildren = true;
console.log('Selecting parent.');
if (this.item.children.length === 1) {
this.item.children[0].selected = this.item.selected;
console.log('Only one child, so setting same as parent.');
} else {
this.item.children.map(child => {
child.selected = true;
console.log(`${child.label} selected: ${child.selected}`);
if (child.children) {
console.log(`${child.label} has children, so do them too.`);
}
});
}
}
items: TreeMenuItem[] = [
{
label: 'Plants',
value: 'plants',
children: [
{
label: 'Mono-Cotyledons',
value: 'monocotyledons',
children: [
{
label: 'Coconut',
value: 'coconut',
selected: false,
indeterminate: false,
disabled: false,
},
{ label: 'Banana', value: 'banana', selected: false, indeterminate: false, disabled: false },
],
selected: false,
indeterminate: false,
disabled: false,
},
{
label: 'Di-Cotyledons',
value: 'dicotyledons',
children: [{ label: 'Mango', value: 'mango', selected: false, indeterminate: false, disabled: false }],
selected: false,
indeterminate: false,
disabled: false,
},
],
selected: false,
indeterminate: false,
disabled: false,
},
];
I am rendering it as such:
render() {
return (
<div>
<li>
{this.item.children ? <button onClick={this.toggleDisplayChildren}>{this.displayChildren ? 'v' : '>'}</button> : ''}
{this.showCheckbox ? <input type="checkbox" checked={this.item.selected} onClick={() => this.selectItem()} style={{ color: 'red' }} /> : ''}
{this.item.label}
</li>
{this.item.children && this.displayChildren
? this.item.children.map(child => {
child.parent = this.item;
return <menu-item item={child} class="child-item"></menu-item>;
})
: ''}
</div>
);
}
Hello i am trying to display different charts using the chartjs by calling the API. Below code shows how i have formatted the chart.vue
Chart.vue:
<template>
<div class="chart-container" style="position: relative; height: 40vh; width:100%;">
<slot name="test1"></slot>
<slot name="test2"></slot>
</div>
</template>
<script>
export default {
name: 'charts',
data () {
return {
date: [],
challenge: [],
data: []
}
},
mounted () {
this.check(8, 'chart_8')
this.check(7, 'chart_7')
},
methods: {
check (id, name) {
this.$http.get(`/api_chart/${ id }/full`)
.then((response) => {
this.date = response.data.date
this.challenge = response.data.challenge
this.data = this.date.map((date, index) => ({
x: new Date(date * 1000),
y: this.challenge[index]
}))
const ctx = document.getElementById([name]).getContext('2d')
let myChart = new Chart(ctx, {
type: 'line',
data: {
datasets: [
{
label: 'Challenge',
data: this.data,
borderColor: ' #EA5455',
}
]
},
options: {
lineTension: 0,
maintainAspectRatio: false,
scales: {
yAxes: [
{
scaleLabel: {
display: false
},
ticks: {
beginAtZero: true,
callback (value) {
return `${value}%`
}
}
}
],
xAxes: [
{
type: 'time',
time: {
unit: 'month'
},
scaleLabel: {
display: true,
}
}
]
}
}
})
})
}
}
}
</script>
App.vue:
<template>
<div class="In order to display chart1">
<chart-display> <canvas slot="test1" id="chart_7" ></canvas> </chart-display>
</div>
<div class="In order to display chart1">
<chart-display> <canvas slot="test2" id="chart_8" ></canvas> </chart-display>
</div>
</template>
<script>
import chart-display from './Chart.vue'
export default {
component: {chart-display}
}
</script>
As you can see i have shared my Chart.vue and App.vue, i am able to see my chart in the browser, but whenever i run the code or refresh the page, the charts flickers and stops. And then in my console i get below error:
Please someone help me to get rid of this issue, and please tell me if any changes i should do in my code to solve it. Please send me the modification code.
As I wrote in my comment, the charts are rendered twice. This causes flickering.
// every time you use <chart-display>, 2 charts are rendered, this means chart 1 renders
// itself and chart 2, char 2 renders itself and chart 1, this is a bad pattern in Vue in general
mounted() {
this.check(8, "chart_8");
this.check(7, "chart_7");
}
Make the following changes:
ChartDisplay.vue
<template>
<div
class="chart-container"
style="position: relative; height: 40vh; width: 100%"
>
<canvas ref="chart_7"></canvas>
<canvas ref="chart_8"></canvas>
</div>
</template>
<script>
import Chart from "chart.js";
export default {
name: "ChartDisplay",
data() {
return {
date: [],
challenge: [],
data: [],
// save charts in an array
charts: [],
// charts options
options: {
lineTension: 0,
maintainAspectRatio: false,
scales: {
yAxes: [
{
scaleLabel: {
display: false,
},
ticks: {
beginAtZero: true,
callback(value) {
return `${value}%`;
},
},
},
],
xAxes: [
{
type: "time",
time: {
unit: "month",
},
scaleLabel: {
display: true,
},
},
],
},
},
};
},
mounted() {
this.render(7, this.$refs.chart_7);
this.render(8, this.$refs.chart_8);
},
methods: {
render(id, ctx) {
this.fetchData(id).then((response) => {
let data = response.date.map((date, index) => ({
x: new Date(date * 1000),
y: response.challenge[index],
}));
this.charts.push(
new Chart(ctx, {
type: "line",
data: {
datasets: [
{
label: "Challenge",
data: data,
borderColor: " #EA5455",
},
],
},
options: this.options,
})
);
});
},
fetchData(id) {
return this.$http.get(`/api_chart/${ id }/full`);
},
},
beforeDestroy() {
this.charts.forEach((chart) => chart.destroy());
},
};
</script>
<style >
[v-cloak] {
display: none;
}
</style>
App.vue
<template>
<div>
<div class="In order to display chart1">
<chart-display/>
</div>
</div>
</template>
<script>
import ChartDisplay from "./ChartDisplay.vue";
export default {
components: { ChartDisplay },
};
</script>
See it on sandbox
I found several errors on your code. I fix them in Sandbox
For Chat.vue :
I rename the file as ChartDisplay.vue as similar as the component name
import chart.js package for using Chart() function
I use a demo API
<template>
<div
class="chart-container"
style="position: relative; height: 40vh; width: 100%"
>
<slot name="test1"></slot>
<slot name="test2"></slot>
</div>
</template>
<script>
import Chart from "chart.js";
export default {
name: "ChartDisplay",
data() {
return {
date: [],
challenge: [],
data: [],
};
},
mounted() {
this.check(8, "chart_8");
this.check(7, "chart_7");
},
methods: {
check(id, name) {
fetch(
"https://api.wirespec.dev/wirespec/stackoverflow/fetchchartdataforvuejs"
)
.then((response) => response.json())
.then((response) => {
this.date = response.date;
this.challenge = response.challenge;
this.data = this.date.map((date, index) => ({
x: new Date(date * 1000),
y: this.challenge[index],
}));
const ctx = document.getElementById([name]).getContext("2d");
new Chart(ctx, {
type: "line",
data: {
datasets: [{
label: "Challenge",
data: this.data,
borderColor: " #EA5455",
}, ],
},
options: {
lineTension: 0,
maintainAspectRatio: false,
scales: {
yAxes: [{
scaleLabel: {
display: false,
},
ticks: {
beginAtZero: true,
callback(value) {
return `${value}%`;
},
},
}, ],
xAxes: [{
type: "time",
time: {
unit: "month",
},
scaleLabel: {
display: true,
},
}, ],
},
},
});
});
},
},
};
</script>
For App.vue
Your import should not carry any hyphen.
component should be components
render the component once to avoid flikering
<template>
<div>
<div class="In order to display chart1">
<chart-display>
<canvas slot="test1" id="chart_7"></canvas>
<canvas slot="test2" id="chart_8"></canvas>
</chart-display>
</div>
</div>
</template>
<script>
import ChartDisplay from "./ChartDisplay.vue";
export default {
components: {
ChartDisplay
},
};
</script>
I have a problem, I need to make an expansion row so I did something like:
<q-table
:title="title"
:data="driverTable.data"
:columns="driverTable.columns"
:visible-columns="driverTable.visibleCols"
row-key="objectId"
:loading="driverTable.loading"
:filter="driverTable.filter"
>
<template
v-slot:body-cell-expandingrow="cellProperties"
>
<q-td
auto-width
:props="cellProperties"
>
<q-btn
size="sm"
color="accent"
round
dense
#click="cellProperties.expand = !cellProperties.expand"
:icon="cellProperties.expand ? 'remove' : 'add'"
/>
</q-td>
<q-tr
v-show="cellProperties.expand"
:props="cellProperties"
>
<q-td
colspan="100%"
>
<div
class="text-left"
>
This is expand slot for row above: {{ cellProperties.row.name }}.
</div>
</q-td>
</q-tr>
</template>
...
data() {
return {
driverTable: {
filter: '',
loading: false,
columns: [
{
name: 'objectId',
field: row => row.objectId,
format: val => '',
},
{
name: 'name',
label: 'Nom complete',
align: 'left',
field: row => row.name,
format: val => `${val}`,
sortable: true,
classes: 'ellipsis',
},
{
name: 'phone',
required: true,
label: 'Téléphone',
field: row => row.phone,
format: val => `${val}`,
},
{
name: 'position',
required: true,
label: 'Position Actuelle',
field: row => `${row.latitude}, ${row.longitude}`,
format: val => `${val}`,
},
{
name: 'carName',
required: true,
label: 'Voiture',
field: row => row.carName,
format: val => `${val}`,
sortable: true,
},
{
name: 'carSerie',
required: true,
label: 'Série',
field: row => row.carSerie,
format: val => `${val}`,
},
{
name: 'carRef',
required: true,
label: 'Référence',
field: row => row.carRef,
format: val => `${val}`,
},
{
name: 'carImage',
required: true,
label: 'Image',
field: row => row.carImage,
align: 'center',
format: val => `${val}`,
}
],
data: []
Just like quasar documentation but it display the expanding row just like another td on the table.
I know that the problem is when I make the td of btn with the row but as U see I dont use on my html any element jsut q-table to display the data table.
here is the result:
I need to display an icon before the selected value using react-select and typescript.
This is what I tried so far:
SingleValue: React.SFC<SingleValueProps<OptionType>> = ({ children, ...props }) => (
<components.SingleValue {...props}>
<i className={`fal fa-${this.props.icon} has-text-grey-light`} /> {children}
</components.SingleValue>
)
The main issue is with type definitions that expects that children passed to components.SingleValue must be a string.
You don´t have to use the standard components. You can easlily create a custom one but still keep the styles it needs.
The only requirement you need is emotion to get the styles the SingleValue component uses.
/**
* This Example uses the `FontAwesome` React library.
**/
const options = [
{ label: "Value A", value: "a", icon: faCoffee },
{ label: "Value B", value: "b", icon: faCar },
{ label: "Value C", value: "c", icon: faMobile },
{ label: "Value D", value: "d", icon: faCircle },
{ label: "Value E", value: "e", icon: faSquare }
];
const SingleValue = ({
cx,
getStyles,
selectProps,
data,
isDisabled,
className,
...props
}) => {
console.log(props);
return (
<div
className={cx(
emotionCss(getStyles("singleValue", props)),
{
"single-value": true,
"single-value--is-disabled": isDisabled
},
className
)}
>
<FontAwesomeIcon icon={data.icon} style={{ marginRight: 10 }} />
<div>{selectProps.getOptionLabel(data)}</div>
</div>
);
};
export default class MySelect extends Component {
render() {
return (
<Select
options={options}
components={{ SingleValue }}
styles={{
singleValue: (provided, state) => ({
...provided,
display: "flex", // To keep icon and label aligned
alignItems: "center"
})
}}
/>
);
}
}
Working example