Vue Array prop default value doesn't work - javascript

I do the same as in the following Vue 2 - How to set default type of array in props
Vue.component("Test-Attribute", {
props: {
attributes: {
type: Array,
required: false,
// Object or array defaults must be returned from
// a factory function
default: function () {
return [
{attributeName: 'One', multiselect: 0},
{attributeName: 'Two', multiselect: 1},
{attributeName: 'Three', multiselect: 0},
{attributeName: 'Four', multiselect: 1},
]
}
}
},
template: `
<div>
<component
v-for="(attribute,index) in attributes"
:key="index"
:name="attribute.attributeName"
:is="getAttributeType(attribute)"
>
{{ attribute.attributeName + ':' }}
</component>
</div>
`,
created() {
console.log(this.attributes)
},
methods: {
getAttributeType: function (attribute) {
return parseInt(attribute.multiselect) === 1 ? 'MultiAttribute' : 'SingleAttribute'
}
}
});
Updated the original question with the full component code, I I pass down a prop the component is rendered as expected

The syntax is OK, I passed down an empty array and props default value is used only if the value is null or undefined.

Related

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>

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

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.

Dynamic lookup from React's state in material-table

I'm using the material-table component, which I fill with dynamic data coming from Redux (an array of objects), but then I do other things with that data inside my component's state. To create column dropdown filters, there's an element inside each column's array of options, lookup, that receives an object and creates the dropdown based on it's values.
I am extracting some items from my data and putting them inside an element in my component's state. This is an object, the same kind that lookup receives. The thing is that the component shows an empty dropdown, as if the object was empty, but it's not. I'm logging it in into the console and the object is filled with the data I need.
I initially thought it was a render problem, that the object is empty at the beggining, and then it's filled with data, but the component renders every time.(Yeah, React is reactive).
This is only the code needed to help me solve this problem:
Table component
import React, { Component } from "react";
import MaterialTable from "material-table";
class CustomTable extends Component {
state = {
column1: "",
column2: "",
column3: "",
column1FilterList: {}
columns: [
{
title: "Column1",
field: "column1",
editable: "onAdd",
filtering: true,
lookup: { ...this.column1FilterList }
},
{
title: "Column2",
field: "column2",
editable: "onAdd",
filtering: true,
},
{
title: "Column3",
field: "column3",
editable: "onAdd",
filtering: true
}
]
};
componentDidMount() {
this.props.fetchValues()
this.props.fetchApplications()
this.filterColumn1ExistingKeys()
}
filterColumn1ExistingKeys = () => {
return this.props.elements.map(element => {
return this.setState(prevState => ({
column1FilterList: {
...prevState.column1FilterList,
[element.name]: element.name
}
}))
})
}
render() {
return (
<div>
<MaterialTable
options={{
search: false,
actionsColumnIndex: 4,
filtering: true
}}
title="Search by"
columns={this.state.columns}
data={this.state.data}
/>
</div>
);
}
}
export default CustomTable;
The problem is how you save that data. You create a new object in the constructor with { ...this.column1FilterList }. This will create a new object which will act as the lookup object, which is filled with the initial data of column1FilterList (empty). Updating the column1FilterList later does not change that lookup object, because it is disconnected (new object). You have to update the lookup within the columns as well like this:
const filterColumn1ExistingKeys = () => {
const column1FilterList = this.state.column1FilterList;
this.props.elements.forEach(element => column1FilterList[element.name] = element.name)
this.setState({
column1FilterList,
columns: [{
title: "Column1",
field: "column1",
editable: "onAdd",
filtering: true,
lookup: { ...column1FilterList }
},
{
title: "Column2",
field: "column2",
editable: "onAdd",
filtering: true,
},
{
title: "Column3",
field: "column3",
editable: "onAdd",
filtering: true
}
]
})
}
Hope this helps. Let me know, if that works for you. If you have any questions, let me know. Happy coding.

Vue property definition warning even though it is defined on the instance

Edit - I have setup a repo on github with the erraneous code here if anyone wants to pull this down and see the error for themselves: https://github.com/andrewjrhill/what-the-instance-grid. You can run npm run serve to kick off the webserver.
I am running into an issue where my Vue is throwing the following errors:
[Vue warn]: Property or method "columns" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
[Vue warn]: Property or method "items" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
This is a pretty common issue with Vue apps and is usually the result of a property not being defined on a Vue data object. Unfortunatley in this case I have indeed added columns and itemsto the new Vue call. Any ideas why I am getting this error? It looks like data isn't available at all to the template.
This project was generated by the latest Vue-CLI and is using the runtimeCompiler: true flag in a vue.config.js file if that makes any difference.
The .vue file in question:
<template>
<div id="vueapp" class="vue-app">
<Grid :columns="columns" :data-items="items" :style="{ height: '280px' }"></Grid>
</div>
</template>
<script>
import Vue from "vue";
import { Grid } from "#progress/kendo-vue-grid";
Vue.component("Grid", Grid);
new Vue({
el: "#vueapp",
data: function() {
return {
items: [],
columns: [
{ field: "ProductID" },
{ field: "ProductName", title: "Product Name" },
{ field: "UnitPrice", title: "Unit Price" }
]
};
},
methods: {
createRandomData(count) {
const productNames = [
"Chai",
"Chang",
"Syrup",
"Apple",
"Orange",
"Banana",
"Lemon",
"Pineapple",
"Tea",
"Milk"
];
const unitPrices = [12.5, 10.1, 5.3, 7, 22.53, 16.22, 20, 50, 100, 120];
return Array(count)
.fill({})
.map((_, idx) => ({
ProductID: idx + 1,
ProductName:
productNames[Math.floor(Math.random() * productNames.length)],
UnitPrice: unitPrices[Math.floor(Math.random() * unitPrices.length)]
}));
}
},
mounted() {
this.items = this.createRandomData(50);
}
});
export default {
name: "App",
components: {
Grid
}
};
</script>
Don't reinstantiate Vue inside the App.vue component.
Fix like this (files from your repo):
main.js:
import App from './App.vue'
import Vue from 'vue'
import { Grid } from "#progress/kendo-vue-grid";
Vue.component("Grid", Grid);
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#vueapp')
App.vue:
<template>
<div id="vueapp" class="vue-app">
<Grid :columns="columns" :data-items="items" :style="{ height: '280px' }"></Grid>
</div>
</template>
<script>
export default {
name: "App",
data: function() {
return {
items: [],
columns: [
{ field: "ProductID" },
{ field: "ProductName", title: "Product Name" },
{ field: "UnitPrice", title: "Unit Price" }
]
};
},
methods: {
createRandomData(count) {
// your code
}
},
mounted() {
this.items = this.createRandomData(50);
}
};
</script>

Categories

Resources