How to make this vue component reusable with it's props? - vue.js

I am trying to make this component reusable so later can install it in any project and via props add needed values e.g. images and function parameters (next, prev, intervals...) inside any component.
<template>
<div>
<transition-group name='fade' tag='div'>
<div v-for="i in [currentIndex]" :key='i'>
<img :src="currentImg" />
</div>
</transition-group>
<a class="prev" #click="prev" href='#'>❮</a>
<a class="next" #click="next" href='#'>❯</a>
</div>
</template>
<script>
export default {
name: 'Slider',
data() {
return {
images: [
'https://cdn.pixabay.com/photo/2015/12/12/15/24/amsterdam-1089646_1280.jpg',
'https://cdn.pixabay.com/photo/2016/02/17/23/03/usa-1206240_1280.jpg',
'https://cdn.pixabay.com/photo/2015/05/15/14/27/eiffel-tower-768501_1280.jpg',
'https://cdn.pixabay.com/photo/2016/12/04/19/30/berlin-cathedral-1882397_1280.jpg'
],
timer: null,
currentIndex: 0,
}
},
mounted: function() {
this.startSlide();
},
methods: {
startSlide: function() {
this.timer = setInterval(this.next, 4000);
},
next: function() {
this.currentIndex += 1
},
prev: function() {
this.currentIndex -= 1
}
},
computed: {
currentImg: function() {
return this.images[Math.abs(this.currentIndex) % this.images.length];
}
}
}
</script>
styles...
So later it would be <Slider... all props, images loop here/> inside other components.
How can be it be achieved?

Just move what needs to come from another component to props. That way other component can pass the relevant info it needs.
export default {
name: 'Slider',
props: {
images: Array,
next: Function
prev: Function,
// and so on
},
...
The parent component would call it like:
<Slider :images="imageArray" :next="nextFunc" :prev="prevFunc" />
EDIT
You can pass an interval value via props:
export default {
name: 'Slider',
props: { intervalVal: Number },
methods: {
startSlide: function() {
this.timer = setInterval(this.next, this.intervalVal);
},
}
You can also pass function from parent to child via props.
export default {
name: 'Slider',
props: { next: Function },
methods: {
someMethod: function() {
this.next() // function from the parent
},
}
I don't really understand your use case 100% but these are possible options.

Related

Vue/Vuex dynamic component not changing

I have a vuex store of "nodes". Each one has a type of Accordion or Block.
{
"1":{
"id":1,
"title":"Default title",
"nodes":[],
"type":"Block"
},
"2":{
"id":2,
"title":"Default title",
"nodes":[],
"type":"Accordion"
}
}
When I use the type to create a dynamic component it works great:
<ul>
<li v-for="(node, s) in nodes" :key="parentId + s">
<component :is="node.type" :node="node" :parent-id="parentId"></component>
</li>
</ul>
But when I change it, nothing happens in the view layer:
convert(state, { to, id }) {
state.nodes[id].type = to;
Vue.set(state.nodes[id], "type", to);
},
I even use Vue.set. How can I make this update?
It updates immediately if I then push another node into the array.
CodeSandbox:
https://codesandbox.io/s/romantic-darwin-dodr2?file=/src/App.vue
The thing is that your getter will not work, because it's not pure: Issue. But you can use deep watcher on your state instead:
<template>
<div class="home">
<h1>Home</h1>
<Sections :sections="nodesArr" :parent-id="null"/>
</div>
</template>
<script>
// # is an alias to /src
import Sections from "#/components/Sections.vue";
import { mapState } from "vuex";
export default {
name: "home",
components: {
Sections
},
data: () => {
return {
nodesArr: []
};
},
computed: {
...mapState(["nodes", "root"])
},
watch: {
root: {
handler() {
this.updateArr();
},
deep: true
}
},
mounted() {
this.updateArr();
},
methods: {
updateArr() {
this.nodesArr = this.root.map(ref => this.nodes[ref]);
}
}
};
</script>

Vue pie chart show after a while (like show console with a warning)

I am trying to fill a pie chart on my vue application, I can correctly fill data into it, but the page didn't show immediately the pie chart, but after a while (like if a show console), and I got a warning in console :
vue.esm.js?efeb:628 [Vue warn]: Invalid prop: type check failed for
prop "chartData". Expected Object, got Null
found in
--->
at src/components/StastCard.vue
at src/App.vue
Here my code (Maybe there was another way to fill data, but I only succesfully done it in this way):
StastCard.vue:
<template>
<div>
<div class="container">
<div class="row">
<div class="col-sm">
<pie-chart :chartData="dataChart"></pie-chart>
</div>
<div class="col-sm"></div>
<div class="col-sm"></div>
</div>
</div>
</div>
</template>
<script>
import DataService from '#/services/DataService'
import PieChart from "#/plugins/PieChart.js";
export default {
name: 'StastCard',
props: {
username: {
type: String
}
},
components: {
PieChart
},
data: function() {
return {
dataChart: {
labels: ["Km", "KJ", "HB"],
datasets: [
{
label: "Data One",
backgroundColor: ["#41B883", "#E46651", "#00D8FF"],
data: [1, 10, 5]
}
]
},
}
},
methods: {
async addData() {
this.firstValue=DataService.getFirstValue()
this.secondValue=DataService.getSecondValue()
this.thirdValue=DataService.getThirdValue()
this.dataChart.labels.pop()
this.dataChart.labels.pop()
this.dataChart.labels.pop()
this.dataChart.labels.push(["Km"])
this.dataChart.labels.push(["KJ"])
this.dataChart.labels.push(["HB"])
this.dataChart.datasets[0].data.pop()
this.dataChart.datasets[0].data.pop()
this.dataChart.datasets[0].data.pop()
this.dataChart.datasets[0].data.push(this.firstValue)
this.dataChart.datasets[0].data.push(this.secondValue)
this.dataChart.datasets[0].data.push(this.thirdValue)
},
},
mounted() {
this.addData()
}
}
</script>
And here my PieChart.js
import { Pie, mixins } from 'vue-chartjs'
export default {
extends: Pie,
props: ['chartData', 'options'],
mounted() {
this.renderChart(this.chartData, this.options)
}
}
What am I doing wrong? Why my pie chart is not immediately displayed? Thank you
First, I think you might want to use reactiveProp to make your chart reactive with data changes.
Secondly, because of vue-chartjs will render child component before parent component, so you will get the Invalid prop warning. To fix it, you can change from mounted to created hook. You can find more information here.
import { Pie, mixins } from 'vue-chartjs'
export default {
extends: Pie,
mixins: [mixins.reactiveProp],
created() {
this.renderChart(this.chartData, {})
}
}
Lastly, you should assign chartData object to a new reference to make Vue reactive. An easy way is using JSON.parse(JSON.stringify())
methods: {
async addData() {
this.firstValue=DataService.getFirstValue()
this.secondValue=DataService.getSecondValue()
this.thirdValue=DataService.getThirdValue()
this.dataChart.labels.pop()
this.dataChart.labels.pop()
this.dataChart.labels.pop()
this.dataChart.labels.push(["Km"])
this.dataChart.labels.push(["KJ"])
this.dataChart.labels.push(["HB"])
this.dataChart.datasets[0].data.pop()
this.dataChart.datasets[0].data.pop()
this.dataChart.datasets[0].data.pop()
this.dataChart.datasets[0].data.push(this.firstValue)
this.dataChart.datasets[0].data.push(this.secondValue)
this.dataChart.datasets[0].data.push(this.thirdValue)
this.dataChart = JSON.parse(JSON.stringify(this.dataChart))
},
},
I finally found a solution, I change the .js file with this:
import { Pie } from 'vue-chartjs'
export default {
extends: Pie,
props: {
chartdata: {
type: Object,
default: null
},
options: {
type: Object,
default: null
}
},
methods: {
renderpie() {
this.renderChart(this.chartdata, this.options)
}
},
mounted() {}
}
Here my view:
<template>
<div>
<div class="container">
<div class="row">
<div class="col-sm">
<pie-chart :chartData="dataChart"></pie-chart>
</div>
<div class="col-sm"></div>
<div class="col-sm"></div>
</div>
</div>
</div>
</template>
<script>
import DataService from '#/services/DataService'
import PieChart from "#/plugins/PieChart.js";
export default {
name: 'StastCard',
props: {
username: {
type: String
}
},
components: {
PieChart
},
data: function() {
return {
dataChart: {},
firstValue:'',
secondValue:'',
thirdValue:''
}
},
methods: {
addData() {
this.firstValue=DataService.getFirstValue()
this.secondValue=DataService.getSecondValue()
this.thirdValue=DataService.getThirdValue()
var hrate = []
this.heart_rate.forEach(el => {
hrate.push(el.rate)
})
this.dataChart = {
labels: ['Km', 'Kj', 'HB'],
datasets: [
{
label: 'Data One',
backgroundColor: ['#41B883', '#E46651', '#00D8FF'],
data: [this.firstValue,this.secondValue,this.thirdValue]
}
]
}
},
},
async created() {
await this.addData()
this.$refs.pie.renderpie()
}
}
</script>

Is it possible to dynamically add chart type in the extends: property, based on props from parent component?

I have a vue chartjs component which imports the whole vue-chartjs library. My idea is, is it possible to somehow pass the type of the chart which I want and add it to the 'extends: VueCharts.charttype?.' In the example I provide it extends the VueCharts.Line, I need this property to be dynamically interpolated, passed from props. Is it possible this charttype to come from a parent props dynamically and how?
<script>
import { VueCharts } from "vue-chartjs";
export default {
extends: VueCharts.Line,
props: ["chartdata", "options"],
mounted() {
this.renderChart(this.chartdata, this.options);
}
}
</script>
<style scoped>
</style>
since extends the same as mixins, you need to pass a dynamic mixin, in order to do that you need two components, imagine we have component ChartWrapper :
<template>
<div>
<div>{{ chartType }}</div>
<chart :chart-data="datacollection"/>
</div>
</template>
<script>
import Chart from "./Chart";
import { VueCharts, mixins } from "vue-chartjs";
const { reactiveProp } = mixins;
export default {
name: "ChartWrapper",
components: {
Chart
},
props: {
chartType: {
type: String,
required: true
}
},
data() {
return {
datacollection: {
labels: [this.getRandomInt(), this.getRandomInt()],
datasets: [
{
label: "Data One",
backgroundColor: "#f87979",
data: [this.getRandomInt(), this.getRandomInt()]
},
{
label: "Data One",
backgroundColor: "#f87979",
data: [this.getRandomInt(), this.getRandomInt()]
}
]
}
};
},
methods: {
getRandomInt() {
return Math.floor(Math.random() * (50 - 5 + 1)) + 5;
}
},
created() {
if (this.chartType) {
Chart.mixins = [reactiveProp,VueCharts[this.chartType]];
}
}
};
</script>
this component takes chartType as a prop, and I import all charts as VueCharts in top of the script ==> 1
second component:
<script>
export default {
props: ["options"],
mounted() {
// this.chartData is created in the mixin.
// If you want to pass options please create a local options object
this.renderChart(this.chartData, this.options);
}
};
</script>
the second component just has options props, and renderChart function invoked.
==> 2
What is happening?
the ChartWrapper component receives the chart type by chartType prop, in the created hook, if chartType exist, assign the chart(resolved by VueCharts[this.chartType]) to Chart component as a mixin in addition to reactiveProp,
I also pass the chart data to Chart component.
in the end, call the ChartWrapper component:
<ChartWrapper chartType="Bar"/>
Live example on code sandbox: https://codesandbox.io/s/vue-template-w9r8k
You can also choose for the option to just extend the Line chart and update the config of the chart with the chart type you want and give it an update so it changes type.
<script>
import { Line, mixins } from 'vue-chartjs';
const { reactiveProp } = mixins;
export default {
extends: Line,
name: "LineChart",
mixins: [reactiveProp],
props: {
options: { type: Object },
chartType: { type: String }
},
mounted () {
this.renderChart(this.chartData, this.options);
},
watch: {
options: {
deep: true,
handler () {
this.$data._chart.options = this.options;
this.updateChart();
}
},
chartType (newVal) {
this.$data._chart.config.type = newVal;
this.updateChart()
}
},
methods: {
updateChart () {
this.$data._chart.update();
},
}
}
</script>

VueJS set custom component default value from props

Hi guys I tried to create a VueJS custom component to wrap Vue Autonumeric component.
https://github.com/autoNumeric/vue-autoNumeric
In Vue Autonumeric page it specifically mention the caveat
Caveats Please note that directly setting a :value='42' on the
component will break it (really!). Do NOT do that:
So in my custom component MoneyComponent.vue, I create a v-model
This is the full code
<template>
<div>
<vue-autonumeric
v-model="amount"
></vue-autonumeric>
</div>
</template>
<script>
import VueAutonumeric from 'vue-autonumeric/src/components/VueAutonumeric.vue';
export default {
components: {
VueAutonumeric,
},
props: {
value: {},
},
data() {
return {
amount: this.value,
}
},
methods: {
},
watch: {
amount (value) {
this.$emit('input', value);
}
},
}
</script>
Usage example
<template>
<v-money
v-model="price"
></v-money>
</template>
<script>
export default {
data() {
return {
price: 45,
}
},
methods: {
}
}
<script>
This works on on initial value from parent. However if I change the price property to 55 for example, the amount property in MoneyComponent is not changing.
What is the problem here the amount property is not reactive on second changes? How do I fix it?
Thanks
Because you're using v-model, you need to emit input event to make data changed in the parent
watch: {
amount: function (newVal) {
this.$emit('input', newVal)
},
value: function (newVal, oldVal) {
if (newVal !== oldVal) {
this.amount = newVal
}
}
}
then your component
<template>
<div>
<vue-autonumeric
v-model="amount"
></vue-autonumeric>
</div>
</template>
<script>
import VueAutonumeric from 'vue-autonumeric/src/components/VueAutonumeric.vue';
export default {
components: {
VueAutonumeric,
},
props: {
value: {},
},
data() {
return {
amount: this.value,
}
},
watch: {
amount: function (newVal) {
this.$emit('input', newVal)
},
value: function (newVal, oldVal) {
if (newVal !== oldVal) {
this.amount = newVal
}
}
}
}
</script>

Make Chartkick wait for data to populate from Firebase before rendering chart

I'm using chartkick in my Vue project. Right now, the data is loading from Firebase after the chart has rendered, so the chart is blank. When I change the code in my editor, the chart renders as expected, since it's already been retrieved from Firebase. Is there a way to make chartkick wait for the data to load before trying to render the chart? Thanks!
Line-Chart Component:
<template>
<div v-if="loaded">
<line-chart :data="chartData"></line-chart>
</div>
</template>
<script>
export default {
name: 'VueChartKick',
props: ['avgStats'],
data () {
return {
loaded: false,
chartData: this.avgStats
}
},
mounted () {
this.loaded = true
}
}
</script>
Parent:
<template>
...
<stats-chart v-if="avgStatsLoaded" v-bind:avgStats="avgStats" class="stat-chart"></stats-chart>
<div v-if="!avgStatsLoaded">Loading...</div>
...
</template>
<script>
import StatsChart from './StatsChart'
export default {
name: 'BBall',
props: ['stats'],
components: {
statsChart: StatsChart
},
data () {
return {
avgStatsLoaded: false,
avgStats: []
}
},
computed: {
sortedStats: function () {
return this.stats.slice().sort((a, b) => new Date(b.date) - new Date(a.date))
}
},
methods: {
getAvgStats: function () {
this.avgStats = this.stats.map(stat => [stat.date, stat.of10])
this.avgStatsLoaded = true
}
},
mounted () {
this.getAvgStats()
}
}
modify your code of StatsChart component:
you may use props directly
<template>
<div v-if="loaded">
<line-chart :data="avgStats"></line-chart>
</div>
</template>
export default {
name: 'VueChartKick',
props: ['avgStats'],
data () {
return {
loaded: false,
}
},
mounted () {
this.loaded = true
}
}