I've just recently started to dive in into Vue JS - loved it so far. I'm facing an issue now where I'm trying to create a (non-trivial) table (using vue-good-table plugin) in which each cell is a component by it's own.
Having read the documentation of the plugin, it's being mentioned that it's possible to create an HTML column types where you can just use, well, a raw HTML (I guess): https://xaksis.github.io/vue-good-table/guide/configuration/column-options.html#html
To simplify things, here's what I have - a Vue component (called Dashboard2.vue) that holds the table and the child component called Test.vue
I'm creating the Test components dynamically per each relevant cell and assigning it to the relevant row cell. since I've defined the columns to be HTML types, I'm using the innerHTML property to extract the raw HTML out of the Vue component. (following this article https://css-tricks.com/creating-vue-js-component-instances-programmatically/) It all goes very well and the dashboard looks exactly how I wanted it to be, but when clicking the button inside each Test component, nothing happens.
I suspect that since I've used the innerHTML property it just skips Vue even handler mechanism somehow, so I'm kinda stuck.
Here's the relevant components section:
Dashboard2.vue:
<template>
<div>
<vue-good-table
:columns="columns"
:rows="rows"
:search-options="{enabled: true}"
styleClass="vgt-table condensed bordered"
max-height="700px"
:fixed-header="true"
theme="black-rhino">
</vue-good-table>
</div>
</template>
<script>
import axios from 'axios';
import Vue from 'vue';
import { serverURL } from './Config.vue';
import Test from './Test.vue';
export default {
name: 'Dashboard2',
data() {
return {
jobName: 'team_regression_suite_for_mgmt',
lastXBuilds: 7,
builds: [],
columns: [
{
label: 'Test Name',
field: 'testName',
},
],
rows: [],
};
},
methods: {
fetchResults() {
const path = `${serverURL}/builds?name=${this.jobName}&last_x_builds=${this.lastXBuilds}`;
axios.get(path)
.then((res) => {
this.builds = res.data;
this.builds.forEach(this.createColumnByBuildName);
this.createTestsColumn();
this.fillTable();
})
.catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
});
},
createBaseRow(build) {
return {
id: build.id,
name: build.name,
cluster: build.resource_name,
startTime: build.timestamp,
runtime: build.duration_min,
estimatedRuntime: build.estimated_duration_min,
result: build.result,
};
},
addChildRows(build, children) {
const row = this.createBaseRow(build);
// eslint-disable-next-line no-plusplus
for (let i = 0; i < build.sub_builds.length; i++) {
const currentBuild = build.sub_builds[i];
if (currentBuild.name === '') {
this.addChildRows(currentBuild, children);
} else {
children.push(this.addChildRows(currentBuild, children));
}
}
return row;
},
createColumnByBuildName(build) {
this.columns.push({ label: build.name, field: build.id, html: true });
},
addRow(build) {
const row = this.createBaseRow(build);
row.children = [];
this.addChildRows(build, row.children);
this.rows.push(row);
},
createTestsColumn() {
const build = this.builds[0];
const row = this.createBaseRow(build);
row.children = [];
this.addChildRows(build, row.children);
// eslint-disable-next-line no-plusplus
for (let i = 0; i < row.children.length; i++) {
this.rows.push({ testName: row.children[i].name });
}
},
fillBuildColumn(build) {
const row = this.createBaseRow(build);
row.children = [];
this.addChildRows(build, row.children);
// eslint-disable-next-line no-plusplus
for (let i = 0; i < row.children.length; i++) {
const childBuild = row.children[i];
const TestSlot = Vue.extend(Test);
const instance = new TestSlot({
propsData: {
testName: childBuild.name,
result: childBuild.result,
runTime: childBuild.runtime.toString(),
startTime: childBuild.startTime,
estimatedRunTime: childBuild.estimatedRuntime.toString(),
},
});
instance.$mount();
this.rows[i] = Object.assign(this.rows[i], { [build.id]: instance.$el.innerHTML });
}
},
fillTable() {
this.builds.forEach(this.fillBuildColumn);
},
},
created() {
this.fetchResults();
},
};
</script>
<style scoped>
</style>
Test.vue
<template>
<div>
<b-card :header="result" class="mb-2" :bg-variant="variant"
text-variant="white">
<b-card-text>Started: {{ startTime }}<br>
Runtime: {{ runTime }} min<br>
Estimated: {{ estimatedRunTime }} min
</b-card-text>
<b-button @click="sayHi" variant="primary">Hi</b-button>
</b-card>
</div>
</template>
<script>
export default {
name: 'Test',
props: {
id: String,
testName: String,
build: String,
cluster: String,
startTime: String,
runTime: String,
estimatedRunTime: String,
result: String,
},
computed: {
variant() {
if (this.result === 'SUCCESS') { return 'success'; }
if (this.result === 'FAILURE') { return 'danger'; }
if (this.result === 'ABORTED') { return 'warning'; }
if (this.result === 'RUNNING') { return 'info'; }
return 'info';
},
},
methods: {
sayHi() {
alert('hi');
},
},
};
</script>
<style scoped>
</style>
I know this is a lot of code. The specific relevant section (in Dashboard2.vue) is fillBuildColumn
Again - I'm a newbie to Vue JS - that being said my hunch tells me I'm doing many things wrong here.
Any help will be greatly appreciated.
EDIT:
by loosing the innerHTML property and the html type I'm ending up with a:
"RangeError: Maximum call stack size exceeded" thrown by the browser. Not sure what's causing it