Vue.js progress bar with values from API call - vue.js

I've got a simple component that pulls data from an API and I would like to add a progress bar that indicates the next data refresh. The API calls are done with a 10 seconds interval and data itself is usually refreshed every minute.
The value for the progress bar is based on date: 2018-12-04T16:10:09.508367Z where I retrieve seconds usingmoment library: moment().diff(2018-12-04T16:10:09.508367Z, 'seconds')
Because this can be a random value the steps for the progress bar are not equal and they don't start from a full bar.
I was wondering if there is a way to improve this?
This is my pseudo code on codesandbox to illustrate that.
<template>
<b-container fluid>
<b-row>
<b-col md="12" class="p-0 fixed-top">
<b-progress
:value="progressCounter"
:max="progressMax"
height="5px"
variant="warning"
>
</b-progress>
</b-col>
</b-row>
<b-row>
<b-col md="12" class="">
{{ progressMax }} / {{ progressCounter }}
</b-col>
</b-row>
</b-container>
</template>
<script>
export default {
name: "ProgressBar",
data() {
return {
progressCounter: 60,
progressMax: 60,
interval: null,
refresh: 10000,
time: 0
};
},
methods: {
getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
},
progress() {
/*
this.time is just a pseudo code here, the real progressCounter is recived from
an API call and seconds are calculated by deducting time from now:
this.progressCounter = 60 - moment().diff(created, 'seconds')
"created" comes from an API call and it's refreshed (~) every minute
*/
if (this.time <= 10) {
this.time = this.getRandomInt(40, 59);
} else {
this.time -= 10;
}
this.progressCounter = this.time;
console.log(
`this.time: ${this.time}, this.progressCounter: ${this.progressCounter}`
);
}
},
mounted() {
this.interval = setInterval(
function() {
this.progress();
}.bind(this),
this.refresh
);
},
beforeDestroy() {
clearInterval(this.interval);
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>

Related

Vue- watch for audio time change

I have an audio file which plays songs. I am currently making a slider that is equal to the songs length and current time. You can't use player.currentTime in the watch component in Vue so how would you go about updating a value in realtime equal to player current time.
I currently have the v-model="player.currentTime" but that only updates when I pause the songs and not real-time.
This is what I have so far
player: new Audio()
this.player.src = this.songs[this.songIndex].src
this.player.play()
Player:
<input
type="range"
name="timeStamp"
ref="time"
v-model.lazy="this.player.currentTime"
step="0.1"
class="w-[100%] hover:cursor-pointer"
/>
You have to listen to the timeupdate event. I made a simple sample code:
Output:
<template>
<div style="border: 1px solid gray; border-radius: 5px; padding: 5px;">
<div>
<button #click="play">Play | Pause</button>
{{ timeLabel }}
</div>
<div>
<input
type="range"
:min="0"
:max="duration"
v-model="currentTime"
#input="updateTime"
>
</div>
</div>
</template>
<script>
export default {
name: 'BaseAudioPlayerTest',
data() {
return {
src: 'Spring-Flowers.mp3',
player: null,
duration: 0,
currentTime: 0,
timeLabel: '00:00:00',
};
},
methods: {
play() {
if (this.player.paused) {
this.player.play();
this.duration = this.player.duration;
} else {
this.player.pause();
}
},
updateTime() {
this.player.currentTime = this.currentTime;
},
timeupdate() {
this.currentTime = this.player.currentTime;
const hr = Math.floor(this.currentTime / 3600);
const min = Math.floor((this.currentTime - (hr * 3600)) / 60);
const sec = Math.floor(this.currentTime - (hr * 3600) - (min * 60));
this.timeLabel = `${hr.toString()
.padStart(2, '0')}:${min.toString()
.padStart(2, '0')}:${sec.toString()
.padStart(2, '0')}`;
},
},
mounted() {
this.player = new Audio(this.src);
this.player.addEventListener('timeupdate', this.timeupdate, false);
},
};
</script>
You can find more info here.

Multiple Stopwatch using VUe

I am a newbie to Vue.
I am working on multiple stopwatch using Vue.
I'm stuck, becuase my component is updating the values on all instances of the components, instead of just in one.
This is what I tried:
<div id="app">
<user-name></user-name>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.js"></script>
<script type='text/x-template' id="test-template">
<div>
<div class="column" v-for="(item, index) in items" :key="index">
<div class="ui segment">
<h3 class="ui blue header">{{parentTitle}}</h3>
<h2 class="ui greenheader">{{item.name}}</h2>
<div class="column">
<p class="ui huge header">
{{ hours }} :
{{ minutes | zeroPad }} :
{{ seconds | zeroPad }} :
{{ milliSeconds | zeroPad(3) }}</p>
<button class="ui secondary button" #click="startTimer"
:disabled="isRunning">START</button>
<button class="ui button" #click="pushTime" :disabled="!isRunning">LAP</button>
<button class="ui button" #click="stopTimer" :disabled="!isRunning">STOP</button>
<button class="ui basic button" #click="clearAll">CLEAR</button><br><br>
<ul class="ui bulleted list" v-if="times.length">
<li class="item" v-for="item in times">
{{ item.hours }} :
{{ item.minutes | zeroPad }} :
{{ item.seconds | zeroPad }} :
{{ item.milliSeconds | zeroPad(3) }}
</li>
</ul>
<br><br>
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('user-name', {
data() {
return {
parentTitle: "Employee Names",
test: "welcome",
times: [],
animateFrame: 0,
nowTime: 0,
diffTime: 0,
startTime: 0,
isRunning: false,
items: [{
id: 1,
name: 'Employee 1'
},
{
id: 2,
name: 'Employee 2'
}
],
count: 0
}
},
template: '#test-template',
methods: {
// 現在時刻から引数に渡した数値を startTime に代入
setSubtractStartTime: function (time) {
var time = typeof time !== 'undefined' ? time : 0;
this.startTime = Math.floor(performance.now() - time);
},
// タイマーをスタートさせる
startTimer: function () {
// loop()内で this の値が変更されるので退避
var vm = this;
//console.log(this);
//alert(timer0.innerText);
vm.setSubtractStartTime(vm.diffTime);
// ループ処理
(function loop() {
vm.nowTime = Math.floor(performance.now());
vm.diffTime = vm.nowTime - vm.startTime;
vm.animateFrame = requestAnimationFrame(loop);
}());
vm.isRunning = true;
//alert(innerText);
},
// タイマーを停止させる
stopTimer: function () {
this.isRunning = false;
cancelAnimationFrame(this.animateFrame);
},
// 計測中の時間を配列に追加
pushTime: function () {
this.times.push({
hours: this.hours,
minutes: this.minutes,
seconds: this.seconds,
milliSeconds: this.milliSeconds
});
},
// 初期化
clearAll: function () {
this.startTime = 0;
this.nowTime = 0;
this.diffTime = 0;
this.times = [];
this.stopTimer();
this.animateFrame = 0;
}
},
computed: {
// 時間を計算
hours: function () {
return Math.floor(this.diffTime / 1000 / 60 / 60);
},
// 分数を計算 (60分になったら0分に戻る)
minutes: function () {
return Math.floor(this.diffTime / 1000 / 60) % 60;
},
// 秒数を計算 (60秒になったら0秒に戻る)
seconds: function () {
return Math.floor(this.diffTime / 1000) % 60;
},
// ミリ数を計算 (1000ミリ秒になったら0ミリ秒に戻る)
milliSeconds: function () {
return Math.floor(this.diffTime % 1000);
}
},
filters: {
// ゼロ埋めフィルタ 引数に桁数を入力する
// ※ String.prototype.padStart() は IEじゃ使えない
zeroPad: function (value, num) {
var num = typeof num !== 'undefined' ? num : 2;
return value.toString().padStart(num, "0");
}
}
});
new Vue({
el: "#app",
});
</script>
Here is a working JSFiddle sample here
Any help highly appreciated.
Here is a solution to your problem jsfiddle
In your code you are mixing the data form your vue instance, with the data of your component.
Instead of having 1 component you can add your component multiple times with the v-for
This is your vue instance + data:
new Vue({
el: "#app",
data() {
return {
people: [{
id: 1,
name: 'Employee 1'
},
{
id: 2,
name: 'Employee 2'
}
]
}
}
});
The solution is to pass the data of the person to a component via props (here called item), and render this component as many time as needed in the array. This way, each component is a independent "instance".
<user-name v-for="(person, index) in people" :key="person.id" :item="person"></user-name>
Vue.component('user-name', {
props:['item'],
.....

Passing Array as prop not received on the other component

I am trying to pass an array of objects as a prop to a component. The Array is being passed without an array. I am neither receiving any compilation error.
I tried actually looking on to the object tried some stuff. But it did not work
Here is the code:
CardRenderer.vue:
<template lang="html">
<div>
<b-container class="bv-example-row">
<b-row v-for="(row, i) of rows" v-bind:key="i">
<b-col v-for="(item, j) of row" v-bind:key="j" >
<!-- you card -->
<b-card
:title="item.title"
img-src="item.icon"
img-alt="Image"
img-top
tag="article"
style="max-width: 20rem;"
class="mb-2"
>
<b-card-text>
<h1>{{item.name}}</h1>
<pre>{{item.description}}</pre>
</b-card-text>
<b-button :href="'/dashboard/'+item.name" variant="primary">More</b-button>
</b-card>
</b-col>
</b-row>
</b-container>
</div>
</template>
<script lang="js">
export default {
name: 'CardRenderer',
props: {
renderData: {
type: Array,
required: true,
default: () => ([]),
}
},
data() {
return {
rows: null
}
},
mounted() {
const itemsPerRow = 3
let rowss = []
// eslint-disable-next-line
console.log(this.renderData)
let arr = this.renderData
for (let i = 0; i < arr.length; i += itemsPerRow) {
let row = []
for (let z = 0; z < itemsPerRow; z++) {
row.push(arr[z])
}
rowss.push(row)
}
this.rows = rowss
// eslint-disable-next-line
// console.log(this.rows)
},
methods: {
},
computed: {
// rows() {
// }
}
}
</script>
<style scoped>
</style>
Something.vue
<template lang="html">
<!-- <h1>Something</h1> -->
<CardRenderer :renderData=valObj />
</template>
<script lang="js">
import CardRenderer from './CardRenderer'
export default {
name: 'something',
components: {
CardRenderer
},
props: [],
data() {
return {
valObj: []
}
},
mounted() {
let key = this.findUrl()
let value = this.$store.getters.responseAPI.apps.filter((elem) => {
if(elem.name == key) return elem.apps
})
if (value && value.length > 0)
this.valObj = value[0].apps
//eslint-disable-next-line
console.log(this.valObj)
},
methods: {
findUrl() {
let url = window.location.pathname.split("/").slice(-1)[0];
return url
}
},
computed: {
}
}
</script>
<style scoped >
.something {
}
</style>
This is what i am sending as a prop.
This is what i receive on the component
There's a couple of issues here.
First, you should be using kebab-cased attribute names and quotes around the value...
<CardRenderer :render-data="valObj" />
The second issue is timing related. In your component, you calculate rows based on the initial renderData in the mounted hook but this will not update when the parent component alters valObj.
What you should do instead is use a computed property which will react to valObj / renderData changes.
For example
data () { return {} }, // removed rows from data
computed: {
rows () {
let itemsPerRow = 3
let rows = []
for (let i = 0; i < this.renderData.length; i += itemsPerRow) {
rows.push(this.renderData.slice(i, i + itemsPerRow))
}
return rows
}
}

how to render array stored in vuex

I am trying to render array of objects stored in store in the form of cards. But, I am not able to. Since it shows typeError.
It states
"error in render: "TypeError: Cannot read property 'item' of undefined"
I tried using this keyword and shifting the code to mounted() hook.
But, the error keeps on showing.
Here is the code:
CardRenderer.vue:
<template lang="html">
<div>
<b-container class="bv-example-row">
<b-row v-for="(row, i) in this. rows" v-bind:key="i">
<b-col v-for="(item, j) in row" v-bind:key="j" >
<!-- you card -->
<b-card
:title="item.title"
img-src="item.icon"
img-alt="Image"
img-top
tag="article"
style="max-width: 20rem;"
class="mb-2"
>
<b-card-text>
<h1>{{item.name}}</h1>
<pre>{{item.description}}</pre>
</b-card-text>
<b-button :href="'/dashboard/'+this.item.name" variant="primary">More</b-button>
</b-card>
</b-col>
</b-row>
</b-container>
</div>
</template>
<script lang="js">
export default {
name: 'CardRenderer',
props: {
},
data() {
return {
// rows: []
}
},
mounted() {
},
methods: {
},
computed: {
rows() {
const itemsPerRow = 3
var rows = []
let arr = this.$store.getters.responseAPI.apps
// eslint-disable-next-line
console.log(arr)
for (let i = 0; i < arr.length; i += itemsPerRow){
let row = []
for (let z = 0; z < itemsPerRow; z++) {
row.push(arr[z])
}
rows.push(row)
}
// eslint-disable-next-line
// console.log(this.rows)
return rows[0]
}
}
}
</script>
<style scoped>
</style>
This is how the error looks like.
and this is how the rows object looks like
How do i remove the error and the render the card.
I would love to have the changed code as the answer.
Thanks :)
I think you have a mistake at the computed property return statement.
Try replacing return rows[0] to return rows to return an array instead of the first item :)
I've finally caught an error))
This is my code sample at sandbox: https://codesandbox.io/embed/vue-template-sm0yx
You've got a mistake at the template, just remove this from :href="'/dashboard/'+this.item.name" to make it look like this: :href="'/dashboard/'+item.name"
That should work!))

How can I call a method every x seconds?

I need to call the method getBitcoins() every second.
Tried: I tried just taking it out the methods and putting it under the
line import Pickaxe from '../assets/pickaxe.png' and then using
setInterval to call it every second, but then I can't access the data
variable btcPrice inside getBitcoins().
So I need a way to call the getBitcoins() from the methods functions every second, just as it is in the code below.
<template>
<div id="wrapper">
<div class="top"></div>
<!-- Center -->
<div class="center">
<img :src="Pickaxe" class="Pickaxe">
<span class="my-btc">{{ mybtc.toFixed(8) }}</span>
<span id="btc">1 BTC = {{ btcPrice }}</span>
<button class="Mine">Mine</button>
<span class="hashes">{{btcMin}} btc/min</span>
<button class="Upgrade">UPGRADE</button>
<span class="upgradePrice">{{ upgradePrice }}btc</span>
</div>
</div>
</template>
<script>
import bitcoin from '../assets/bitcoin.svg'
import Pickaxe from '../assets/pickaxe.png'
export default {
name: 'landing-page',
data() {
return {
bitcoin,
Pickaxe,
mybtc: 1,
btcPrice: null,
btcMin: 0,
upgradePrice: 0
}
},
methods: {
getBitcoins() {
var currentPrice = new XMLHttpRequest();
currentPrice.open('GET', 'https://api.gdax.com/products/BTC-USD/book', true);
currentPrice.onreadystatechange = function(){
if(currentPrice.readyState == 4){
let ticker = JSON.parse(currentPrice.responseText);
let price = ticker.bids[0][0];
document.getElementById('btc').innerHTML = "1 BTC = " + price + "$";
};
};
currentPrice.send();
}
}
}
</script>
I think this should work for your needs.
created() {
this.interval = setInterval(() => this.getBitcoins(), 1000);
},
It's not necessary to register this on the created event, you can register it on other method, or even on a watcher.
If you do it that way, you'll have to check somehow that it hasn't been registered, cause it may cause multiple loops to run simultaneously.