VueJS - v-for getting undefined value from props nested object - vue.js

I'm new to VueJS.
My component's template and the props are below
<template>
<div>
<label>WorkHours</label>
<div v-for="(data, day) in value.config.workingHours">
<label>{{day}}</label>
<hour-range-selector
:value="[data.timeFrom, data.timeTo]"
class="rangeText"
:mandatory="true"
:placeholder="placeholder"
:full-range="['00:00', '23:59']"
#input="(val) => workingHoursChanged(val, day)"
/>
</div>
</div>
</template>
<script>
import HourRangeSelector from '..../HoursRangeSelector';
export default {
name: 'WorkingDaysSelector',
components: {HourRangeSelector},
props: {
value: {
config:{
workingHours: {
monday: {
available: false,
timeFrom: '',
timeTo: '',
},
tuesday: {
available: false,
timeFrom: '',
timeTo: '',
},
wednesday: {
available: false,
timeFrom: '',
timeTo: '',
},
thursday: {
available: false,
timeFrom: '',
timeTo: '',
},
friday: {
available: false,
timeFrom: '',
timeTo: '',
},
saturday: {
available: false,
timeFrom: '',
timeTo: '',
},
sunday: {
available: false,
timeFrom: '',
timeTo: '',
}
}
}
},
placeholder: {
type: Array,
default: () => (['From', 'To']),
},
},
data() {
return {
fullRange: ['00:00', '23:59'],
};
},
methods:{
workingHoursChanged(val, day){
//IN PROGRESS
}
};
</script>
<style scoped>
</style>
If I run the code, I'm getting
vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in render: "TypeError: Cannot read property 'workingHours' of undefined"
Am I approaching correctly? Or How do I achieve this?

Props are the data passed from the parent component to the child one, if you want to define local properties you should define them inside the data option like :
export default {
name: 'WorkingDaysSelector',
components: {HourRangeSelector},
props: {
placeholder: {
type: Array,
default: () => (['From', 'To']),
},
},
data() {
return {
value: {
config:{
workingHours: {
monday: {
available: false,
timeFrom: '',
timeTo: '',
},
tuesday: {
available: false,
timeFrom: '',
timeTo: '',
},
wednesday: {
available: false,
timeFrom: '',
timeTo: '',
},
thursday: {
available: false,
timeFrom: '',
timeTo: '',
},
friday: {
available: false,
timeFrom: '',
timeTo: '',
},
saturday: {
available: false,
timeFrom: '',
timeTo: '',
},
sunday: {
available: false,
timeFrom: '',
timeTo: '',
}
}
}
},
fullRange: ['00:00', '23:59'],
};
},
methods:{
workingHoursChanged(val, day){
//IN PROGRESS
}
};
</script>
<style scoped>
</style>

Related

Vue : Echart js how to export chart as image (Vuexy admin.)

I'm attempting to export the chart as a picture. I just joined eCharts. Can someone explain how the export button operates? I need to download charts as image.
Here's what I try : Do I have something wrong?
<template>
<b-card title="Data Science">
<app-echart-bar:option-data="option"/>
</b-card>
</template>
<script>
import { BCard } from 'bootstrap-vue'
import AppEchartBar from '#core/components/charts/echart/AppEchartBar.vue'
export default {
components: {
BCard,
AppEchartBar,
},
data() {
return {
option: {
toolbox: {
show: true,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar', 'stack'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
xAxis: [
{
type: 'category',
data: ['FB', 'IN', 'TW', 'YT',],
},
],
yAxis: [
{
type: 'value',
splitLine: { show: false },
},
],
grid: {
left: '40px',
right: '30px',
bottom: '30px',
},
series: [
{
name: 'Available',
type: 'bar',
barGap: 0,
emphasis: {
focus: 'series'
},
data: [22, 24, 20, 18]
},
{
name: 'Not Available',
type: 'bar',
barGap: 0,
emphasis: {
focus: 'series'
},
data: [12, 10, 14, 16]
},
],
},
}
},
}
</script>
Comp:
<template>
<e-charts
ref="line"
autoresize
:options="option"
theme="theme-color"
auto-resize/>
<script>
import ECharts from 'vue-echarts'
import 'echarts/lib/component/tooltip'
import 'echarts/lib/component/legend'
import 'echarts/lib/chart/bar'
import theme from './theme.json'
ECharts.registerTheme('theme-color', theme)
export default { components: {
ECharts,
}, props: {
optionData: {
type: Object,
default: null,
}, }, data() {
return {
option: {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
toolbox: {
show: true,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar', 'stack'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
legend: {
left: 0,
},
grid: this.optionData.grid,
xAxis: this.optionData.xAxis,
yAxis: this.optionData.yAxis,
series: this.optionData.series,
},
} }, }
</script>
The chart created by eCharts is shown below :
Can somebody assist me with adding a button or tell me whether I need to add a script to download an image.

Vue2 trying to set chart title

Does not work like this:
<template>
<Bar
:chart-options="chartOptions"
:chart-data="$attrs['chart-data'] || chartData"
:chart-id="chartId"
:dataset-id-key="datasetIdKey"
:plugins="plugins"
:css-classes="cssClasses"
:styles="styles"
:width="width"
:height="height"
/>
</template>
<script>
import { Bar } from 'vue-chartjs/legacy'
import 'chart.js/auto'
export default {
name: 'BarChart',
components: { Bar },
props: {
chartId: {
type: String,
default: 'bar-chart',
},
datasetIdKey: {
type: String,
default: 'label',
},
width: {
type: Number,
default: 400,
},
height: {
type: Number,
default: 400,
},
cssClasses: {
default: '',
type: String,
},
styles: {
type: Object,
default: () => {},
},
plugins: {
type: Object,
default: () => {},
},
},
data() {
return {
chartData: {},
chartOptions: {
responsive: true,
title: {
display: true,
text: 'Custom Chart Title',
},
},
}
},
}
</script>
tried editing even with this: https://codesandbox.io/s/exciting-ully-2wst65?file=/src/components/Bar.vue
but nothing works.
All info gathered from: https://vue-chartjs.org/api/
Registration of components are not needed if you import auto.
maybe some ideas?
You are using v2 syntax in v3, the internal plugins like the tooltip, title, legend and decimation have been moved from the options namespace to the options.plugins namespace.
So you need to nest the title options in a plugins object within the options object. Then it will work
For those like me, who are looking for a solution using vue-chartjs V4.
To show the Title you need to pass the plugin object, for example:
plugins: {
type: Array,
default: () => [Title]
}
and then pass into the chartOptions props the title configuration, for example:
chartOptions: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: "Chart Title",
},
},
}
sandBox full example:

vue doesn't update the child chart component in echart

I have built a vue component to show a PIE chart in echart library as showed below. The PIE chart will be initialized with a default value.
pieChart.vue
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import resize from './mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
},
chartData: {
type: Object,
required: true
}
},
watch: {
chartData: function(val){
console.log('chartdata handler',val);
this.setOptions(val.legend, val.data);
}
},
data() {
return {
chart: null
}
},
mounted() {
this.$nextTick(() => {
this.initChart()
})
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons');
this.setOptions(
['group_a','group_b','group_c'],
[
{ value: 1, name: 'group_a' },
{ value: 2, name: 'group_b' },
{ value: 3, name: 'group_c' },
]
);
},
setOptions( lengend, data ) {
this.chart.setOption({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
legend: {
left: 'center',
bottom: '10',
data: lengend
},
series: [
{
name: 'WEEKLY WRITE ARTICLES',
type: 'pie',
roseType: 'radius',
radius: '50%',
data: data,
animationEasing: 'cubicInOut',
animationDuration: 2600
}
]
});
}
}
}
</script>
then I use this component in a view.
<template>
<pie-chart :chartData="updateData"/>
</template>
<script>
export default {
name: 'Personalinforadm',
components: {
PieChart,
},
data() {
return {
updateData: {
data:[
{ value: 33, name: 'group_a' },
{ value: 17, name: 'group_b' },
{ value: 3, name: 'group_c' },
],
legend:['group_a','group_b','group_c']
}
}
},
created() {
this.updateData = {
data:[
{ value: 3, name: 'group_a' },
{ value: 17, name: 'group_b' },
{ value: 3, name: 'group_c' },
],
legend:['group_a','group_b','group_c']
}
}
}
</script>
however the view doesn't update the PIE chart component with the new values in created methods. why the new values doesn't pass to the PIE component and trigger the watch methods, any ideas what goes wrong with the code?
take a look at https://github.com/ecomfe/vue-echarts or solution below may help you.(if you use version >4.x)
sample.vue
<template>
//...
<v-chart
class="chart mt-7"
:option="botChartData"
:update-options="updateOpts"
/>
//...
</template>
<script>
export default {
data() {
return {
updateOpts: {
notMerge: true,
},
botChartData: {
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)",
},
series: [
{
name: "Active Bots",
type: "pie",
center: ["50%", "50%"],
radius: ["75%", "90%"],
itemStyle: {
borderRadius: 8,
},
data: [],
}
],
},
};
},
methods: {
connect() {
this.bots = [
{
value: 0,
name: "a",
},
{
value: 1,
name: "b",
},
{
value: 2,
name: "c",
},
];
this.botChartData.series[0].data = this.bots;
}
},
};
</script>
I called "connect" in "created" you can call it in mounted or on events!
if you need to set your chart as a child component you can easily pass this.botChartData like below
child.vue
<template>
<v-chart
class="chart mt-7"
:option="botData"
:update-options="updateConf"
/>
</template>
<script>
export default {
props: {
botChartData: {
type: Object
},
updateOpts: {
type: Object
}
},
computed: {
botData() {
return this.botChartData
},
updateConf() {
return this.updateOpts
}
}
};
</script>
in parent.vue
<template>
//...
<sample :botChartData="botChartData" :updateOpts="updateOpts" />
//...
</template>
<script>
//...the same as sample.vue js
</script>
By the way if you have multiple charts in one page dont forget notMerge then your charts will reinitialize after switching between them

How to test Vue.js plugin function that return a document element?

I create a simple Vue.js plugin, and want to test that with npm and jest.
One function should return a document element by id, but it return always null.
Why is that and how can i fix it?
// plugintest.js
const PluginTest = {
install(Vue, options) {
Vue.mixin({
methods: {
getBool: function() { return true; },
getElem: function(id) { return document.getElementById(id); }
}
});
}
}
export default PluginTest;
//plugintest.spec.js
let Vue = require('vue/dist/vue')
import PluginTest from './plugintest.js';
Vue.use(PluginTest);
describe("plugintest.js", () => {
test('test functions', () => {
const wrapper = new Vue({
template: '<div id="div1">Hello, World!</div>' }).$mount();
expect(wrapper.getBool()).toBe(true);
expect(wrapper.getElem('div1')).not.toBe(null); // FAIL!!!
});
});
Error message:
expect(received).not.toBe(expected) // Object.is equality
Expected: not null
19 |
20 | expect(wrapper.getBool()).toBe(true);
> 21 | expect(wrapper.getElem('div1')).not.toBe(null);
| ^
22 | });
23 | });
24 |
at Object.toBe (plugintest.spec.js:21:39)
console.log(wrapper.$el) output:
HTMLDivElement {
__vue__:
Vue {
_uid: 1,
_isVue: true,
'$options':
{ components: {},
directives: {},
filters: {},
_base: [Object],
methods: [Object],
template: '<div id="div1">Hello, World!</div>',
render: [Function: anonymous],
staticRenderFns: [] },
_renderProxy:
Vue {
_uid: 1,
_isVue: true,
'$options': [Object],
_renderProxy: [Circular],
_self: [Circular],
'$parent': undefined,
'$root': [Circular],
'$children': [],
'$refs': {},
_watcher: [Object],
_inactive: null,
_directInactive: false,
_isMounted: true,
_isDestroyed: false,
_isBeingDestroyed: false,
_events: {},
_hasHookEvent: false,
_vnode: [Object],
_staticTrees: null,
'$vnode': undefined,
'$slots': {},
'$scopedSlots': {},
_c: [Function],
'$createElement': [Function],
'$attrs': [Getter/Setter],
'$listeners': [Getter/Setter],
_watchers: [Object],
getBool: [Function: bound getBool],
getElem: [Function: bound getElem],
_data: {},
'$el': [Circular] },
_self: [Circular],
'$parent': undefined,
'$root': [Circular],
'$children': [],
'$refs': {},
_watcher:
Watcher {
vm: [Circular],
deep: false,
user: false,
lazy: false,
sync: false,
before: [Function: before],
cb: [Function: noop],
id: 2,
active: true,
dirty: false,
deps: [],
newDeps: [],
depIds: Set {},
newDepIds: Set {},
expression: 'function () {\n vm._update(vm._render(), hydrating);\n }',
getter: [Function: updateComponent],
value: undefined },
_inactive: null,
_directInactive: false,
_isMounted: true,
_isDestroyed: false,
_isBeingDestroyed: false,
_events: {},
_hasHookEvent: false,
_vnode:
VNode {
tag: 'div',
data: [Object],
children: [Object],
text: undefined,
elm: [Circular],
ns: undefined,
context: [Circular],
fnContext: undefined,
fnOptions: undefined,
fnScopeId: undefined,
key: undefined,
componentOptions: undefined,
componentInstance: undefined,
parent: undefined,
raw: false,
isStatic: false,
isRootInsert: true,
isComment: false,
isCloned: false,
isOnce: false,
asyncFactory: undefined,
asyncMeta: undefined,
isAsyncPlaceholder: false },
_staticTrees: null,
'$vnode': undefined,
'$slots': {},
'$scopedSlots': {},
_c: [Function],
'$createElement': [Function],
'$attrs': [Getter/Setter],
'$listeners': [Getter/Setter],
_watchers: [ [Object] ],
getBool: [Function: bound getBool],
getElem: [Function: bound getElem],
_data: {},
'$el': [Circular] } }
Try using vm.$el.
expect(wrapper.$el).not.toBe(null);
The root DOM element that the Vue instance is managing.
Reference
NOTICE
If you want to access another elements, your code could look something like this:
expect(wrapper.$el.querySelector("div")).not.toBe(null);
Another option is to use VueTestUtils's find mthod: https://vue-test-utils.vuejs.org/api/wrapper/find.html#find
Using VueTestUtils
Try using attachToDocument option.
Reference
import { mount, createLocalVue } from "#vue/test-utils";
import PluginTest from "./plugintest.js";
const localVue = createLocalVue();
localVue.use(PluginTest);
it("plugintest.js", () => {
const component = {
template: "<div id="div1">Hello, World!</div>"
}
const wrapper = mount(component, {
localVue,
attachToDocument: true
});
expect(wrapper.vm.getElem("div1")).not.toBe(null);
});
I think you'r missing the parameter for getElem(), try it like:
expect(wrapper.getElem('div1')).not.toBe(null);

Vue.js data changed but view not

I defined an object in data as
export default {
data() {
return {
labelPosition: 'right',
isText: false,
isDate: false,
isExam: false,
isFile: false,
isWrite: false,
stepLists: [],
flowId: '',
txtName: '',
form: {
textName: '',
textPosition: '',
}
the html like this :
when I change the form.textName ,I found it doesn't work
this.$set(this.form, 'textName', temp.name) //not work
this.form={textName:'abc'}
this.form = Object.assign({}, this.form)
//not work
this.$set(this.form,'textName', '---------------------') work well.