#click doesn't work when rendering it using v-html - javascript

I have a component which I wish I could create rows and columns with data and columns object from the parent.
There is a situation where I need to render an html template to create a clickable link, I make it want to display the link in the Actions column. vue version: ^2.5.17
below is the code from parent.vue:
// parent.vue
<table-component :data="category" :column="column" class="bg-red-200 py-4 mt-8"/>
// parent.vue
data(){
return {
modalMode: '',
isShowModalEdit: false,
category: [
{ id: 1, name: 'Jasa Pre-Order', color: '#eb4034' },
{ id: 2, name: 'Jualan', color: '#fcba03' },
{ id: 3, name: 'Jasa Design', color: '#9f34eb' },
],
}
}
// parent.vue
methods: {
toggleModalEdit(){
this.isShowModalEdit = !this.isShowModalEdit
this.modalMode = 'EDIT'
}
}
// parent.vue
computed: {
column() {
return [
{
dataField: 'name',
text: 'Name',
},
{
dataField: 'color',
text: 'Category Color',
formatter: (cell,row) => {
return `
<div style="background-color: ${cell};" class="rounded-full h-8 w-8 flex items-center justify-center mr-2"></div>
<div class="font-bold text-gray-500">${cell}</div>
`
},
classes: (cell, row, rowIndex, colIndex) => {
return 'flex';
}
},
{
dataField: 'actions',
text: 'Actions',
formatter: (cell,row) => {
return `
Edit
`
},
},
]
},
}
and this is the sample code from component.vue:
// component.vue
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="(row, rowIndex) in data" :key="rowIndex">
<td v-for="(col, colIndex) in column" :key="col.dataField" :class=" col.classes ? col.classes(row[col.dataField],row,rowIndex,colIndex) : '' " v-html=" col.formatter ? col.formatter(row[col.dataField],row) : row[col.dataField] " class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"></td>
<tr>
</tbody>
// component.vue
props: {
data: {
type: Array,
required: true
},
column: {
type: Array,
required: true
},
}
the result is like this:
but the link in the Actions column does not work as it should, I hope that when clicking this link will run the method of the perent namely toggleModalEdit. and this is what the link looks like when i inspect it:
i am still new to vue, i am not sure what i did best or not, i hope you guys can help.

Your issue is, that the HTML inside the v-html directive is not processed by Vue's template compiler at all.
Because the compiler doesn't compile this HTML, the #click is not interpreted (but simply rendered without triggering any action).
For this to work, you'd need to iterate over all columns and initialize a new component that handles what's inside the cell yourself directly in HTML (and not in some string that's gonna be rendered later on).
I guess that this is enough - if you still need to interpret what's in the string, you may use Vue.compile to interpret the content. But be careful as it's not safe in case there's some malicious code in it - but since the directive by default has no sanitizing at all, I guess that's just the way Vue.js works.

Thanks to #SimplyComple0x78 for the answer, I marked your suggestions:
For this to work, you'd need to iterate over all columns and initialize a new component that handles what's inside the cell yourself directly in HTML (and not in some string that's gonna be rendered later on).
so I try to create and initialize a new component, I call it element-generator. reference from here. here's the code:
// element-generator.vue
<script>
export default {
render: function (createElement) {
const generatedChildren = (child) => {
if(!child) return // when child of undefined
if(typeof child === 'string') return child // when children is String
return child.map((e,i,a)=>{
if(typeof child[i] == 'string'){
return child[i]
}else{
return createElement(
child[i].tag,
child[i].attributes,
generatedChildren(child[i].children) // javascript recursive
)
}
})
}
return createElement(
this.formatter.html.tag,
this.formatter.html.attributes,
generatedChildren(this.formatter.html.children)
)
},
props: {
formatter: {
type: Object,
required: true
},
},
}
</script>
and I no longer use v-html in component.vue instead I just do a check inside <td> and call element-generator to handle what's inside the cell:
// component.vue
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="(row, rowIndex) in data" :key="rowIndex">
<td v-for="(col, colIndex) in column" :key="col.dataField"
:class=" col.classes ? col.classes(row[col.dataField],row,rowIndex,colIndex) : '' "
class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"
>
<element-generator v-if="col.formatter" :formatter="col.formatter(row[col.dataField],row)"></element-generator>
<div v-else>{{row[col.dataField]}}</div>
</td>
</tr>
</tbody>
and in parent.vue I replaced the String with the Object that will be passed to the element-generator later, it looks like this:
// parent.vue
computed: {
column() {
return [
{
dataField: 'name',
text: 'Name',
},
{
dataField: 'color',
text: 'Category Color',
formatter: (cell,row) => {
return {
html: {
tag: 'div',
attributes: {
class: 'flex'
},
children:[
{
tag: 'div',
attributes: {
style: `background-color: ${cell};`,
class: 'rounded-full h-8 w-8 flex items-center justify-center mr-2',
},
},
{
tag: 'div',
attributes: {
class: 'font-bold text-gray-500',
},
children: cell
},
]
}
}
},
},
{
dataField: 'actions',
text: 'Actions',
formatter: (cell,row) => {
return {
html: {
tag: 'a',
attributes: {
class: 'text-indigo-600 hover:text-indigo-900',
on: {
click: this.toggleModalEdit
},
attrs: {
href: "#"
},
},
children: 'Edit'
},
}
},
},
]
},
},
then when I inspect it in the browser, the result is like this(this is different from the previous one):
and finally what I want to display when Edit is clicked is now displayed:
Thank you very much everyone.

Related

VueGoodTable filter dropdown options vue2

I'm trying to populate possible dropdown options on vue good table. The idea is that I conduct an API call to the server to bring back what values can possibly go into the drop down and I'm trying to assign it to the column filter. However, I can't seem to get it to work.
<vue-good-table
:paginationOptions="paginationOptions"
:sort-options="sortOptions"
:isLoading.sync="isTableLoading"
:rows="rows"
:columns="columns"
:lineNumbers="true"
mode="remote"
:totalRows="totalRecords"
#on-row-click="onRowClick"
#on-page-change="onPageChange"
#on-sort-change="onSortChange"
#on-column-filter="onColumnFilter"
#on-per-page-change="onPerPageChange"
#on-search="onSearch"
:search-options="{
enabled: true
}"
styleClass="vgt-table striped bordered"
ref="table"
>
Fairly standard vgt set up.
columns: [
{
label: 'some column',
field: 'column1'
},
{
label: 'Customer',
field: 'customerName',
filterOptions: {
enabled: true,
placeholder: 'All',
filterDropdownItems: Object.values(this.customers)
}
},
{
label: 'other columns',
field: 'column234'
}
]
and the API call
methods: {
async load () {
await this.getTableOptions()
},
async getTableOptions () {
try {
var response = await axios.get(APICallUrl)
this.customers= []
for (var i = 0; i < response.data.customers.length; i++) {
this.customers.push({ value: response.data.customers[i].customerId, text: response.data.customers[i].customerName})
}
} catch (e) {
console.log('e', e)
}
}
The only thing that I thought of is that the table has finished rendering before the load method is complete. However just creating a static object in my data and assigning it to a filterDropDownItems yielded no better results. The result whenever I try to set it to an object is that the box is a type-in box rather than a dropdown.
You can make the table update after it's rendered by making columns a computed property. The other problem you have is this.customers is an Array but Object.values() expects an Object. You could use the Array.map function instead
this.customers.map(c => c.value)
Although according to the VueGoodTable docs an array of objects like you have should work just fine
computed: {
columns() {
return [
{
label: 'some column',
field: 'column1'
},
{
label: 'Customer',
field: 'customerName',
filterOptions: {
enabled: true,
placeholder: 'All',
filterDropdownItems: this.customers
}
},
{
label: 'other columns',
field: 'column234'
}
];
}
}

Boostrap Vue Change Cell Text in B-table

In a b-table, each cell in a column should have different text color. Not the background of the cell, but the actual text color. I am able to change the text color of the column header, but not the individual cell texts.
The b-table code:
<b-card title="Total">
<b-table sticky-header="600px" hover :items="total" :fields="groupByFields"></b-table>
</b-card>
The fields where the column header color change is:
groupByFields: [
{
key: 'name',
sortable: true,
},
{
label: 'Total',
key: 'count',
},
{
key: 'interested',
thStyle: { color: '#3eef33' },
sortByFormatted: false,
formatter: (value) => {
const res = value;
return (`($${res})`);
},
},
],
The thStyle color attribute changes the header text color, but not the text of values in the cells of that column. How would I match that color to the cell text (not background) of that column too?
Here's an example, using the #cell() slot:
new Vue({
el: '#app',
data: () => ({
items: [],
fields: [
'id',
{ key: 'title', style: { fontStyle: 'italic' } },
{ key: 'price', style: { color: 'red', textAlign: 'right' } }
]
}),
mounted() {
fetch('https://dummyjson.com/products')
.then(_ => _.json())
.then(_ => this.items = _.products);
}
})
<link href="https://unpkg.com/bootstrap#4.6.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.22.0/dist/bootstrap-vue.css" rel="stylesheet"/>
<script src="https://unpkg.com/babel-polyfill/dist/polyfill.min.js"></script>
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.22.0/dist/bootstrap-vue.js"></script>
<div id="app">
<b-table :items="items" :fields="fields">
<template #cell()="{field, value}">
<div :style="field.style" v-text="value" />
</template>
</b-table>
</div>
If you only need to style a few columns, you might want to use appropriate #cell({key}) slots.
Documentation here.
Probably the most common technique is to add classes to cells. It provides granular control over the applied styling, without sacrificing flexibility.

How to trigger sort function in custom v-data-table column headers?

I have a v-data-table that groups items. When using a custom header with v-slot like below - how do I enable the built in sort functionality that's on the default header?
is there a way I can trigger the built in sort function with my sortThisColumn function, or any other way? At the moment, my table headings are unclickable.
<v-app class="v-app-custom" v-if="myItems.length > 0">
<div class="section">
<v-data-table
:headers="headers"
:items="myItems"
:items-per-page="30"
hide-default-footer
hide-default-header
item-key="uuid"
group-by="mentor_id"
class="mentor-table"
>
<template v-slot:header="{ props }">
<thead class="v-data-table-header">
<tr>
<th
class="text-start"
v-for="header in props.headers"
:key="header.value"
:style="{ width: `${header.width}px` }"
#click="sortThisColumn(header.value)"
>
<span>{{ header.text }}</span>
<v-icon v-if="header.sortable" color="white" small>{{
sort[header.value] == "desc"
? "fa-chevron-down"
: "fa-chevron-up"
}}</v-icon>
</th>
</tr>
</thead>
</template>
<template
v-slot:group.header="{ group, props, headers }"
sort-icon="fa-chevron-down"
>
Table headers:-
headers: [
{
text: "Status",
value: "status",
width: 82,
sortable: false,
},
{ text: "Type", value: "type", width: 140, sortable: true },
{ text: "Creator", value: "creator", width: 140, sortable: true },
{ text: "Date", value: "due_date", width: 141, sortable: true },
{ text: "Mentor", value: "mentor", width: 141, sortable: true },
],
Check this codesanbox I made: https://codesandbox.io/s/stack-70975751-p9msn?file=/src/components/CustomSorting.vue
You can define a custom sorting function in your headers array like this. I've done this in the past to add a custom sort to one of my date columns.
headers: [
{
text: 'Date',
value: 'date',
align: 'center',
sort: (a, b) => {
var date1 = a.replace(/(\d+)\/(\d+)\/(\d+)/, '$3/$2/$1')
var date2 = b.replace(/(\d+)\/(\d+)\/(\d+)/, '$3/$2/$1')
return date1 < date2 ? -1 : 1
}
}
]
Knowing this, you could do something like this in your case.
data: (vm) => ({
headers: [
{
text: 'Date',
value: 'date',
sort: (a,b) => {
return vm.myCustomSort(a,b)
}
},
]
})

Change element prop in runtime

I have a chart component, and my job is to make a button to change it's type (eg. columns to pie), but i don't know how to change it on a button click event. Here's the structure of the component (the idea is to change the :series-defaults-type when the button with ChangeType id is pressed)
<template>
<div style="width: 100%;overflow: overlay;border-radius: 20px;">
<button id="changeType" #click="changeType()">Change</button>
<chart-more-option :kpiName="'EquipmentRetirementForecast'" v-if="showMoreOptions"/>
<chart :title-text="'Equipment Retirement Forecast'"
:title-color="'#FFF'"
:title-font="'openSans'"
:chart-area-background="'#1B1534'"
:legend-visible="false"
:series-defaults-type= "'column'"
:series="series"
:category-axis="categoryAxis"
:axis-defaults-color="'#FFF'"
:axis-defaults-labels-rotation-angle="'30'"
:value-axis="valueAxis"
:tooltip="tooltip"
:theme="'sass'"
:zoomable-mousewheel="true">
</chart>
</div>
</template>
<script>
import { Chart } from '#progress/kendo-charts-vue-wrapper';
import ChartMoreOption from '../ChartMoreOption';
export default {
name: 'EquipmentRetirementForecast',
components: {
'chart': Chart,
ChartMoreOption
},
props: {
fetchData: {
type: Boolean,
default: false
},
showMoreOptions: {
type: Boolean,
default: true,
},
},
watch: {
labelAlign(){
var c = this.$refs.chart
c.updateWidget();
}
},
computed:{
requestBody(){
return this.$store.getters['usersession/getTopologyRequestBody']
},
series(){
return this.$store.getters['riskmanagement/getRetirementForecastSeries']
},
categoryAxis(){
return this.$store.getters['riskmanagement/getRetirementForecastCategoryAxis']
},
},
data: function() {
return {
valueAxis: [{
line: {
visible: false
},
minorGridLines: {
visible: true
},
labels: {
rotation: "auto"
}
}],
tooltip: {
visible: true,
template: "#= series.name #: #= value #",
},
}
},
mounted(){
if(this.fetchData){
this.$store.dispatch("riskmanagement/FetchRetirementForecastData",this.requestBody).then(()=>{
});
}
},
methods: {
changeType(){
//code goes here
}
}
}
</script>
<style src="../style-dashboard.scss" lang="scss" scoped />
This is the chart i need to change:
Changing the :series-defaults-type to pie by hand, it works, but i need to make that change in a button click, as follows:
Add a data property and give it the default of 'column', name it for example chartType. Then inside the changeType() you add this.chartType = 'pie'. And change :series-defaults-type= "'column'" to :series-defaults-type= "chartType".
Also remember to NOT use : for attribute values that are hardcoded. So :chart-area-background="'#1B1534'" should be chart-area-background="#1B1534".

API JSON formatting to bootstrap-vue table

I am a student working on a VueJS dashboard displaying scientific research data from clinicaltrials.gov. The issue is the JSON response value is in the form of an array and as such prints with brackets and quotation marks in the table. I am trying to display the data without the brackets and quotations. In addition, when there are multiple items as in the Condition field, I am trying to display each item seperated by commas.
The data comes from the API as an axios response in the following format:
{
"StudyFieldsResponse":{
"APIVrs":"1.01.02",
"DataVrs":"2021:03:18 22:20:36.369",
"Expression":"Pfizer",
"NStudiesAvail":371426,
"NStudiesFound":5523,
"MinRank":1,
"MaxRank":1,
"NStudiesReturned":1,
"FieldList":[
"OrgFullName",
"Acronym",
"InterventionName",
"Condition",
"Phase",
"LastKnownStatus",
"ResultsFirstPostDate",
"LastUpdatePostDate"
],
"StudyFields":[
{
"Rank":1,
"OrgFullName":[
"Pfizer"
],
"Acronym":[],
"InterventionName":[
"Infliximab [infliximab biosimilar 3]"
],
"Condition":[
"Crohn's Disease",
"Ulcerative Colitis"
],
"Phase":[],
"LastKnownStatus":[],
"ResultsFirstPostDate":[],
"LastUpdatePostDate":[
"December 21, 2020"
]
}
]
}
}
Axios Call in mounted:
var APIurl = "https://clinicaltrials.gov/api/query/study_fields?expr=" + TrialSearch + "%0D%0A&fields=OrgFullName%2CAcronym%2CInterventionName%2CCondition%2CPhase%2CLastKnownStatus%2CResultsFirstPostDate%2CLastUpdatePostDate&min_rnk=1&max_rnk=999&fmt=json"
axios
.get(APIurl)
.then(response => {this.items = response.data.StudyFieldsResponse.StudyFields})
.catch(function (error) {
//eslint-disable-next-line no-console
console.log(error);
})
HTML of b-table:
<b-table :items="items" id="table-list" responsive :per-page="perPage" :current-page="currentPage" :fields="fields" :sort-by.sync="sortBy" :sort-desc.sync="sortDesc" >
</b-table>
js data function:
data: function() {
return {
sortBy: 'name',
perPage: 10,
PipeperPage: 5,
currentPage: 1,
sortDesc: false,
sortByFormatted: true,
filterByFormatted: true,
sortable: true,
fields: [
{ key: 'Acronym', sortable: true },
{ key: 'InterventionName', sortable: true },
{ key: 'Condition', sortable: true },
{ key: 'Phase', sortable: true },
{ key: 'LastKnownStatus', sortable: true },
{ key: 'ResultsFirstPostDate', sortable: true },
{ key: 'LastUpdatePostDate', sortable: true },
],
items: [],
screenshot of current table format
Sorry for the long post; I am really trying to learn but am lost.
Like you said all the properties of the response are arrays instead of primitives values like string, boolean, or number. b-table expects that the items data is an array of rows (that is good) with properties and primitives values like you are passing arrays is printing the whole value as a string.
What you need to do is add a computed property (that will change when items change) mapping that item with a new item, choosing the values you use (to keep it simple) and joining the array in a string.
You can choose to join the array arr.join(', ') or to select the first item arr[0] || "".
Life example https://codesandbox.io/s/api-json-formatting-to-bootstrap-vue-table-r91cw?file=/src/components/Dashboard.vue:1095-1586
<template>
<b-table
:items="simpleItems"
id="table-list"
responsive
:per-page="perPage"
:current-page="currentPage"
:fields="fields"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
/>
</template>
<script>
export default {
data() {
return {
// your data
}
},
computed: {
simpleItems() {
return this.items.map((item) => {
return {
Acronym: item.Acronym.join(", "),
InterventionName: item.InterventionName.join(", "),
Condition: item.Condition.join(", "),
Phase: item.Phase.join(", "),
LastKnownStatus: item.LastKnownStatus.join(", "),
ResultsFirstPostDate: item.ResultsFirstPostDate.join(", "),
LastUpdatePostDate: item.LastUpdatePostDate.join(", "),
};
})
},
}
}
</script>

Categories

Resources