How to use customRender in a table cell with Antd and VueJS - vue.js

I'm using antd in my app and I'm trying to do a customRender to show an image in a cell.
My columns array looks like this:
columns: [
{ title: 'Design',
dataIndex: 'designImage.fileUrl',
customRender () { // h will be injected
return '<div id="foo">bar</div>'
}
},
]
So, as you might imagine, it turns out like this:
I also tried this way:
{ title: 'Design',
dataIndex: 'designImage.fileUrl',
customRender: (text, record, index) => {
return {
children: '<img src="https://via.placeholder.com/300.png/09f/fff">'
}
}
},
Unfortunately, that ended up like this:
Can someone please help me figure out what I'm doing wrong?

You can take advantage of the scopedSlots property within columns, and use it to define a scoped slot for the customRender property.
Here is an example:
const columns = [
{
title: "Image",
dataIndex: "image",
key: "image",
scopedSlots: { customRender: "image-column" },
},
];
Now, in your template, you can use the image-column named slot, like this:
<a-table :columns="columns" :data-source="tableData">
<template slot="image-column" slot-scope="image">
<img :src="image" /> <!-- Add your custom elements here -->
</template>
</a-table>
And here is a component example:
<template>
<a-table :columns="columns" :data-source="tableData">
<template slot="image-column" slot-scope="image">
<img :src="image" />
</template>
</a-table>
</template>
<script>
const columns = [
{
title: "Image",
dataIndex: "image",
key: "image",
scopedSlots: { customRender: "image-column" },
},
];
const tableData = [
{
image: "https://picsum.photos/200",
},
{
image: "https://picsum.photos/200",
},
];
export default {
data() {
return {
columns,
tableData,
};
},
};
</script>

When you use antd's Table, customRender should return a vnode but not a string, so you can try it like this
columns: [
{ title: 'Design',
dataIndex: 'designImage.fileUrl',
customRender () {
return <div id="foo">bar</div>
}
}
]

Related

Cannot read properties of undefined on formatter, but data is showing fine

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?

Unable to update Apexchart's category (x-axis label) dynamically in vue

I am using Apexchart's bar chart and noticed that I am not able to change the x-axis's labels, ie the categories. Below is the component:
<template>
<div>
{{magnitudeByFreq}}
{{chartOptions}}
<apex-chart width="500" type="bar" :options="chartOptions" :series="series"></apex-chart>
</div>
</template>
<script>
export default {
props: {
processedMouseData: null,
gradientCountByType: null,
magnitudeByFreq: null,
},
data: function() {
return {
chartOptions: {
chart: {
id: 'vuechart-example'
},
xaxis: {
categories: []//['Positive', 'Neutral', 'Negative']
}
},
series: [{
name: 'series-1',
data: []
}]
}
},
mounted() {
},
watch: {
gradientCountByType: function() {
console.log(this.series.data)
this.gradientCountByType ? this.series[0].data = this.gradientCountByType : console.log("Not working")
this.gradientCountByType ? this.chartOptions.xaxis.categories = ['Positive', 'Neutral', 'Negative'] : console.log("No xaxis")
},
magnitudeByFreq: function() {
this.magnitudeByFreq ? this.series[0].data = Object.values(this.magnitudeByFreq) : console.log("ABX")
this.magnitudeByFreq ? this.chartOptions.xaxis.categories = Object.keys(this.magnitudeByFreq) : console.log("ABA")
}
}
};
</script>
Currently the categories is set to []. This is because I want it to be filled by different data depending on which prop is using it. ie gradientCountByType or magnitudeByFreq.
The two lines below which are supposed to set the category:
this.gradientCountByType ? this.chartOptions.xaxis.categories = ['Positive', 'Neutral', 'Negative'] : console.log("No xaxis")
this.magnitudeByFreq ? this.chartOptions.xaxis.categories = Object.keys(this.magnitudeByFreq) : console.log("ABA")
They don't seem to update the category at all. I should however mention that what gets displayed in the template {{magnitudeByFreq}} and {{chartOptions}}, do reflect there is a change in the category variable:
{{chartOptions}} shows:
{ "chart": { "id": "vuechart-example" }, "xaxis": { "categories": [ "Positive", "Neutral", "Negative" ], "convertedCatToNumeric": false } }
and
{ "chart": { "id": "vuechart-example" }, "xaxis": { "categories": [ "+0", "+100", "+1000", "+2000" ], "convertedCatToNumeric": false } }
Why is the categories attribute not displaying correctly? For whatever reason, the categories are showing numbers.
My guess is by changing the data attribute doesn't actually update the chart.
Instead we should create a reference to the chart:
<apex-chart ref="radar" type="radar" height="350" :options="chartOptions" :series="series"></apex-chart>
Then we can update the data with updateSeries method and update the chartOptions with updateOptions method:
this.$refs.radar.updateSeries([{
name: 'Series 1',
data: [your_new_data_here] //ie [1,2,3,4]
}])
this.$refs.radar.updateOptions({
xaxis: {
categories: [your_new_categories_here] //ie ["a","b","c","d"]
}
})
<apex-chart ref="chart" :options="options" :series="series"/>
Chart component has refresh function. So we can re-render the chart.
this.series[0].data = [yourData];
this.options.xaxis.categories = [yourCategories]
this.$refs.chart.refresh();

How to make nested properties reactive in Vue

I hava a component with complex nested props:
<template>
<div>
<a-tree :tree-data="data" :selected-keys="[selectedKey]" #select="onSelect" />
<div>
<a-input v-model="sheet[selectedKey].tableName" />
<ux-grid ref="previewTable">
<ux-table-column v-for="field in sheet[selectedKey].fields"
:key="field.name" :field="field.name">
<a-input slot="header" v-model="field.label" />
</ux-table-column>
</ux-grid>
</div>
</div>
</template>
<script>
export default {
props: {
previewData: { type: Array, default: () => [] }
},
data () {
return {
data: this.previewData,
selectedKey: '0-0-0',
sheet: { 'none': { tableName: null, fields: [] } }
}
},
created () {
this.data.forEach((file, fid) => {
file.sheets.forEach((sheet, sid) => {
this.$set(this.sheet, `0-${fid}-${sid}`, {
tableName: sheet.label,
fields: sheet.fields.map(field => ({ ...field }))
})
})
})
},
mounted () {
this.$refs.previewTable.reloadData(this.data[0].sheets[0].data)
},
methods: {
onSelect ([ key ], { node }) {
if (key !== undefined && 'fields' in node.dataRef) {
this.selectedKey = key
this.$refs.previewTable.reloadData(node.dataRef.data)
} else {
this.selectedKey = 'none'
this.$refs.previewTable.reloadData()
}
}
}
}
</script>
And previewData props is something like:
{
name: "example.xlsx",
filename: "80b8519f-f7f1-4524-9d63-a8b6c92152b8.xlsx",
sheets: [{
name: "example",
label: "example",
fields:[
{ label: "col1", name: "col1", type: "NUMBER" },
{ label: "col2", name: "col2", type: "STRING" }
]
}]
}
</script>
This component allows user to edit the label properties. I have to make Object sheet reactive to user input, and I tried $set and Object.assign, it works for sheets.label but fields[].label is still not reactive.
I wish to know what would be the declarative (and optimal) solution for it
You might need a watcher or computed property in React for previewData to be changed.

How to make multiple search filters work with a for loop in Vue Bootstrap?

I have a table with some filters in each column but when I type anything all the items disappear. The console does not return any error.
Here’s the code:
<template>
<div>
<b-table
id="the-table"
:per-page="perPage"
:current-page="currentPage"
:items="filteredItems"
:fields="fields"
>
<template slot="top-row" slot-scope="{ fields }">
<td v-for="field in fields" :key="field.key">
<input v-model="filters[field.key]" :placeholder="field.label" />
</td>
</template>
</b-table>
<b-pagination v-model="currentPage" :total-rows="rows" :per-page="perPage" aria-controls="the-table"></b-pagination>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
filters: {
option: "",
style: "",
stock: "",
},
items: [],
fields: [
{ key: "options", label: "Options", sortable: true },
{ key: "style", label: "Style", sortable: true },
{ key: "stock", label: "Stock", sortable: true },
{ key: "type", label: "Type", sortable: true },
{ key: "class", label: "Class.", sortable: true },
],
perPage: 15,
currentPage: 1,
};
},
created() {
this.getTable();
},
methods: {
getTable() {
axios
.get("./data/options.json")
.then((res) => (this.items = res.data))
.catch((error) => console.log(error));
},
},
computed: {
rows() {
return this.items.length;
},
filteredItems() {
return this.items.filter((d) => {
return Object.keys(this.filters).every((f) => {
return this.filters[f].length < 1 || this.filters[f].includes(d[f]);
});
});
},
},
};
</script>
I am trying to filter an array of objects like this:
[{"option": "ABEVH160", "style": "American", "stock": "ABEV3", "type": "CALL", "class": "ITM"},
{"option": "BBAS230", "style": "European", "stock": "BBAS3", "type": "PUT", "class": "ATM"},
{"option": "BBDC180", "style": "American", "stock": "BBDC4", "type": "CALL", "class": "OTM"}]
My goal is to be able to filter each of the columns individually:
Does anyone know what I’m missing?
The problem is with the condition:
return this.filters[f].length < 1 || this.filters[f].includes(d[f]);
d[f] is the full value and this.filters[f] is the search string. Obviously this does not work since it checks if the full word is contained in a substring. Simply invert the condition:
return this.filters[f].length < 1 || d[f].includes(this.filters[f]);
You can see it working here.
What you want seems to be something like DataTable Filter | Filter Row in PrimeVue. I tried to find something similar in the documentation of bootstrap-vue (documentation), but couldn´t find anything like that.
Maybe you are in a stage where you can still implement PrimeVue in your project. I´m using Filter Row from PrimeVue by myself and can recommend it.

Vue Draggable Recursive / Nested Dragging Weird Behavior

This question will be bountied as soon as I can, so a big rep reward coming to someone!
EDIT: One more layer to this issue I noticed, The lowest level children when I try to drag it somewhere else it doesn't work, acts like it will but goes back where it was (i.e it's children are empty). But when I drag the parent of a child somewhere else it does one of the following:
Moves the parent and it's parent somewhere else where I drag it, but leaves the child where it was i.e
Initial state on a refresh
After I drag col-2 to container-1
or
Moves the child into the container where I dragged the parent to but leaves the parent where it was i.e
Initial state on a refresh
After dragging col-2 into col-1
Original Post
Hey guys, been building a landing page builder and decided vue-draggable would be a nice addition. That said after 3 days of headaches trying to make this work I'm at a loss. So far I've followed the nested example guide which has been KINDA working, in addition, I followed an issue about the nested guide on here adding an emitter to the children for proper updates. Now my getters and setters are firing BUT I'm still having a problem dragging elements(see the video)
http://www.giphy.com/gifs/ZMiyi8LEcI73nye1ZN
As you can see when I drag stuff around it's strange behavior:
Example cases:
When I drag col 2 label into col 1 it moves the children inside into
col one, does not change col 2s place
When I drag paragraph label
anywhere it will not move, shows like it will but when I release
nothing happens
If I drag row 1 from the original starting state you
saw in the gif into the paragraph I end up with the following:
Just 3 sample cases, references:
https://sortablejs.github.io/Vue.Draggable/#/nested-with-vmodel
https://github.com/SortableJS/Vue.Draggable/issues/701#issuecomment-686187071
my code creating these results:
component-renderer.vue (THERE'S A NOTE IN HERE TO READ)
<draggable
v-bind="dragOptions"
:list="list"
:value="value"
style="position: relative; border: 1px solid red"
:tag="data.tagName"
:class="data.attributes.class + ` border border-danger p-3`"
#input="emitter"
#change="onChange" //NOTE: I've tried setting the group here to row instead of in the computed prop below, didn't work
>
<slot></slot>
<component-renderer
v-for="el in realValue"
:key="el.attributes.id"
:list="el.children"
:data="el"
:child="true"
#change="onChange"
>
<span style="position: absolute; top: 0; left: 0; background: red">{{
`${el.tagName} - ${el.attributes.id}`
}}</span>
{{ el.textNode }}
</component-renderer>
</draggable>
</template>
<script>
import draggable from "vuedraggable";
export default {
name: "ComponentRenderer",
components: {
draggable,
},
props: {
data: {
required: false,
type: Object,
default: null,
},
value: {
required: false,
type: Array,
default: null,
},
list: {
required: false,
type: Array,
default: null,
},
child: {
type: Boolean,
default: false,
required: false,
},
},
computed: {
dragOptions() {
return {
animation: 0,
disabled: false,
ghostClass: "row",
group: "row",
};
},
realValue() {
return this.value ? this.value : this.list;
},
},
methods: {
emitter(value) {
this.$emit("input", value);
},
onChange: function () {
if (this.child === true) {
this.$emit("change");
} else {
this.emitter(this.value);
}
},
},
};
</script>
<style scoped></style>
PageEditor.vue:
<div id="wysiwyg-page-editor">
<ChargeOverNavBar />
<div class="editor">
<ComponentRenderer v-model="elements" :data="elements[0]" />
</div>
<ChargeOverFooter />
</div>
</template>
<script>
import ChargeOverNavBar from "#/components/ChargeOverNavBar";
import ChargeOverFooter from "#/components/ChargeOverFooter";
import InlineEditor from "#ckeditor/ckeditor5-build-inline";
import ComponentRenderer from "#/components/module-editor/ComponentRenderer";
export default {
name: "PageEditor",
components: {
ComponentRenderer,
ChargeOverFooter,
ChargeOverNavBar,
},
data() {
return {
editor: InlineEditor,
editorConfig: {},
activeSection: null,
page: {},
panels: {
pageProperties: true,
seoProperties: true,
sectionProperties: false,
},
};
},
computed: {
elements: {
get() {
console.log("getter");
return JSON.parse(JSON.stringify(this.$store.state.editor.editorData));
},
set(value) {
console.log("setter");
this.$store.dispatch("setEditor", value);
},
},
},
};
</script>
<style lang="scss" scoped>
#use "../assets/scss/components/PageEditor";
</style>
editor.js(store module):
state: {
editorData: [],
editorLoading: false,
},
mutations: {
SAVE_EDITOR(state, data) {
state.editorData = data;
},
TOGGLE_EDITOR_LOAD(state, busy) {
state.editorLoading = busy;
},
},
actions: {
setEditor({ commit }, data) {
commit("SAVE_EDITOR", data);
},
loadEditor({ commit }) {
commit("TOGGLE_EDITOR_LOAD", true);
//TODO: Change me to read API DATA
let fakeData = [
{
tagName: "section",
attributes: {
id: "section-1",
class: "test",
},
children: [
{
tagName: "div",
attributes: {
id: "container-1",
class: "container",
},
children: [
{
tagName: "div",
attributes: {
id: "row-1",
class: "row",
},
children: [
{
tagName: "div",
attributes: {
id: "col-1",
class: "col",
},
children: [],
},
{
tagName: "div",
attributes: {
id: "col-2",
class: "col",
},
children: [
{
tagName: "p",
attributes: {
id: "p-1",
class: "p",
},
textNode: "This is my paragraph",
children: [],
},
],
},
],
},
],
},
],
},
];
commit("SAVE_EDITOR", fakeData);
commit("TOGGLE_EDITOR_LOAD", false);
},
},
getters: {
getEditorData: (state) => state.editorData,
getEditorLoading: (state) => state.editorLoading,
},
};
It seems only dragging labels works for moving stuff around but not like i'd expect. I think this makes sense but why can't I drag the body anywhere? It's not slotted in header or footer and the docs says that's the onlytime it wouldn't be? Can I not use as the tag itself and drag it?
As I'm sure all of you can deduce the behavior I'm expecting, anything should be draggable into any section(in the future I want this to change so only cols can be dragged into rows, rows can only be dragged into sections etc(but for now I'm not sure how to do this so we start at the beginning :D)).
Also yes I know those components are kinda messy atm, until I fix this I'm not cleaning them up as I keep drastically changing the contents of these files trying to make it work, sorry it's hacky atm!
Any help or ideas would be amazing!
Thanks guys!