Multiple Stopwatch using VUe - vue.js

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'],
.....

Related

v-on does not working why not receive $emit in custom component

I'm create a an component which represents my money field.
My target is on add element in list, set zero on money field to add next element in list...
But, my problem is that not working when send using $emit event to clear input to improve usability.
$emit works as described on image bellow
My money field:
<template>
<div class="input-group" #clear="clearInputField()">
<span>{{ title }}</span>
<input ref="displayMoney" type="text" v-model="displayMoney" #focus="isActive = true" #blur="isActive = false" />
</div>
</template>
<script>
export default {
props: {
title: String,
},
data() {
return {
money: 0,
isActive: false,
};
},
methods: {
clearInputField() {
console.log("Its work event");
this.money = 0;
this.displayMoney = "";
},
},
computed: {
displayMoney: {
get: function () {
if (this.isActive) {
return this.money;
} else {
return this.money.toLocaleString("pt-br", { style: "currency", currency: "BRL" });
}
},
set: function (modifiedMoney) {
let newMoney = parseFloat(modifiedMoney.replace(/[^\d.]/g, "."));
if (isNaN(newMoney) || newMoney.length == 0) {
newMoney = 0;
}
this.$emit("input", newMoney);
return (this.money = parseFloat(newMoney));
},
},
},
};
</script>
My principal component
<template>
<div class="wish-list">
<div class="row">
<div class="input-group">
<span>Digite sua meta: </span>
<input ref="descriptionWish" type="text" v-model="descriptionWish" />
</div>
<MoneyField title="Valor (R$): " v-model="valueWish" #keyup.native.enter="addWish" />
<button id="btnCalculate" #click="addWish()">Adicionar a lista de desejos</button>
</div>
<div class="list-items">
<ul>
<li v-for="wish in wishes" :key="wish">{{ wish }}</li>
</ul>
</div>
</div>
</template>
<script>
import MoneyField from "./Fields/MoneyField";
export default {
components: {
MoneyField,
},
data() {
return {
wishes: [],
valueWish: 0,
descriptionWish: "",
};
},
methods: {
addWish() {
if (!isNaN(this.valueWish) && this.valueWish > 0 && this.descriptionWish.length > 0) {
this.wishes.push(
`${this.descriptionWish} => ${this.valueWish.toLocaleString("pt-BR", { currency: "BRl", style: "currency" })}`
);
this.descriptionWish = "";
console.log("addWish");
this.valueWish = 0;
this.$emit("clear");
this.$refs.descriptionWish.focus();
}
this.valueWish = 0;
},
},
};
</script>
I still don't understand much about vueJS, but I believe it's something related to parent and child elements, but I've done numerous and I can't get my answer.
sorry for my bad english .
The emit sends an event from the child to the parent component not as you've done, to run a method from the child component you could add a ref in the component inside the parent one like :
<MoneyField title="Valor (R$): "
ref="moneyField" v-model="valueWish" #keyup.native.enter="addWish" />
then run this.$refs.moneyField.clearInputField() instead this.$emit("clear")

How do I add min and max value to my Vue count value

The user can click on a + and - button to increment and decrement the value. How do I add a min and max value e.g. min = 1 and max = 10 for the <span>[[ count ]]</span>?
My Vue.js app:
<div id="app">
<a class="btn" v-on:click="increment">Add 1</a>
<a class="btn" v-on:click="decrement">Remove 1</a>
<span>[[ count ]]</span>
</div>
<script>
const App = new Vue({
el: '#app',
delimiters: ['[[',']]'],
data() {
return {
min: 1,
max: 10,
count: 1
}
},
methods: {
increment() {
this.count = this.count === this.max ? this.max : this.count + 1;
},
decrement() {
this.count = this.count === this.min ? this.min : this.count + 1;
}
}
})
</script>
Update:
Above code is working now.
1) How do I change my <span>[[ count ]]</span> into an <input type="number" min="0" max="10" />, controlled by this buttons?
2) How do I add a class e.g disabled when [[ count ]] === 1?
Update 2:
I changed it to an input field:
<input type="number" name="lineItems[{{ product.id }}][quantity]" value="{{ quantity }}" v-model.number="quantity" min="{{ product.minPurchase }}" max="{{ product.calculatedMaxPurchase }}" class="custom-qty__qty-field">
And make the input value adjustable by the min and plus buttons:
<script>
const app = new Vue({
el: '#app',
delimiters: ['[[',']]'],
data() {
return {
quantity: 1,
max: {{ product.calculatedMaxPurchase }},
min: {{ product.minPurchase }}
}
},
methods: {
increment() {
//this.count++
this.quantity = this.quantity === this.max ? this.max : this.quantity + 1;
},
decrement() {
//this.count--
this.quantity = this.quantity === this.min ? this.min : this.quantity - 1;
}
}
})
</script>
E.g {{ product.minPurchase }} are twig variables which contains settings from the ShopWare backend.
Is this a clean way? And how do I add a CSS class when the count reaches 1, so I can disable the button?
Check if the count is already at the limits during increment and decrement and act accordingly.
increment() {
this.count = this.count === 10 ? 10 : this.count + 1;
}
decrement() {
this.count = this.count === 1 ? 1 : this.count - 1;
}
You could also make min and max data properties instead of hardcoding 1 and 10 if you wanted.
After your Edit:
If you use a number input instead, you could solve this without methods. All that's needed is to bind your data to the input like this:
<input type="number" v-model="count" :min="min" :max="max" />
See the demo below:
new Vue({
el: "#app",
data() {
return {
min: 1,
max: 10,
count: 1
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="number" v-model="count" :min="min" :max="max" />
<span class="instructions"><< Mouseover the input to change</span>
<div class="count">Count: {{ count }}</div>
</div>

How do i get the v-model values of a v-for components if i iterate with only numbers?

I have v-for form-group components i iterated over a select's value(integer). I want to get the values of the iterated v-models, but i just can't seem to get it right
TEMPLATE
<b-form-group
id="input-group-1"
label="Jumlah Lowongan:"
label-for="selectJumlahLow"
description="Silahkan pilih satu."
v-if="show"
>
<b-form-select id="selectJumlahLow" v-model="form.jumlahlow" :options="selow" required></b-form-select>
</b-form-group>
<b-form-group label="Nama Lowongan:" v-for="n in parseInt(form.jumlahlow)" :key="n">
<b-form-input required placeholder="Masukkan nama lowongan" v-model="low"></b-form-input>
</b-form-group>
SCRIPT DATA
data() {
return {
form: {
jumlahlow: 1,
checked: [],
low: []
}
}
I've tried changing the model to low[n] or declaring low in data as object {} but either of these seems to be undefined according to TypeErrors i've encoutered.
How am i suppose to get the low[n]'s values?
EDIT:
Here is the full code:
<template>
<div>
<b-form #submit="onSubmit" #reset="onReset">
<b-form-group
id="input-group-1"
label="Jumlah Lowongan:"
label-for="selectJumlahLow"
description="Silahkan pilih satu."
v-if="show"
>
<b-form-select id="selectJumlahLow" v-model="form.jumlahlow" :options="selow" required></b-form-select>
</b-form-group>
<b-form-group label="Nama Lowongan:" v-for="n in parseInt(form.jumlahlow)" :key="n">
<b-form-input required placeholder="Masukkan nama lowongan" v-model="low"></b-form-input>
</b-form-group>
<b-button type="submit" variant="primary">
{{ buttonText }}
<i class="material-icons">arrow_forward_ios</i>
</b-button>
<b-button type="reset" variant="danger">Reset</b-button>
</b-form>
<b-card class="mt-3" header="Form Data Result">
<pre class="m-0">{{ form }}</pre>
</b-card>
</div>
</template>
<script>
export default {
name: "lowonganForm",
data() {
return {
form: {
jumlahlow: 1,
checked: [],
low: []
},
selow: [
{ text: "Pilih Satu", value: null, disabled: true },
1,
2,
3,
4,
5,
6
],
show: true,
target: false,
buttonText: "Next"
};
},
methods: {
onSubmit(evt) {
evt.preventDefault();
alert(JSON.stringify(this.form));
// if (this.jumlahlow !== null || !this.jumlahlow < 1) {
// this.show = false;
// }
},
onReset(evt) {
evt.preventDefault();
// Reset our form values
this.form.jumlahlow = null;
this.form.checked = [];
// Trick to reset/clear native browser form validation state
this.show = false;
this.$nextTick(() => {
this.show = true;
});
}
},
computed: {}
};
</script>
You should try to model your data for how you want the view to be rendered. If you want to have a list of input boxes, then the data for those inputs should be defined in an array that is prepopulated with those items, or when you need to adjust the number of items you should add those data items to the array. You'll avoid reactivity problems this way too.
Here's an example of what I mean:
new Vue({
el: '#app',
data: {
maxCount: 5,
count: 3,
items: [],
data: '',
},
computed: {
visibleItems() {
return this.items.slice(0, this.count)
}
},
created() {
// Define the data upfront so it will be reactive
for (let i = 0; i < this.maxCount; i++) {
this.items.push({
firstName: '',
lastName: '',
})
}
},
methods: {
submit() {
// Transform the data into some JSON that is
// compatible with your API
const data = this.visibleItems.map(item => ({
first_name: item.firstName,
last_name: item.lastName,
role: 'user',
}))
this.data = JSON.stringify(data, null, ' ')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
Number of people:
<select v-model="count">
<option v-for="i of maxCount" :value="i">{{ i }}</option>
</select>
</div>
<div v-for="item of visibleItems">
<input placeholder="First name" v-model="item.firstName">
<input placeholder="Last name" v-model="item.lastName">
</div>
<button #click="submit">Submit</button>
<pre>{{ data }}</pre>
</div>
Try this example.
<div id="app">
<div>
<select v-model="jumlahlow">
<option v-for="i in selects" :key="i">{{ i }}</option>
</select>
</div>
<div v-for="num, index in parseInt(jumlahlow)">
<input v-model="lows[index].value" />
</div>
</div>
And JS
new Vue({
el: '#app',
data: {
lows: [
{
value: ''
}
],
jumlahlow: 1,
selects: [
1,
2,
3,
4,
5,
6
]
},
watch: {
jumlahlow: function (val) {
this.lowsTmp = this.lows;
this.lows = [];
for (let i = 0; i < val; i++) {
const currentVal = typeof this.lowsTmp[i] !== 'undefined' ? this.lowsTmp[i].value : '';
this.addLow(currentVal);
}
}
},
methods: {
addLow: function(val) {
this.lows.push({ value: val });
}
}
})
Directly check here: https://jsfiddle.net/abinhho/m3c8r4tj/2/
you are iterating v-for="n in parseInt(form.jumlahlow)" but that's an Object and v-for works on array not on objects.
Here you can use array of objects to iterate, for example:-
form: [{
jumlahlow: 1,
checked: [],
low: []
}]
and after that you will have to write v-for="n in form" then try accessing low by form.low

Axios response happening after component is rendered

I have a parent component making an Ajax request using Axios, The response is then assigned to a variabled called 'carousel' and is then passed down to the child component.
In the child component on 'created()' I am assigning the passed prop 'carousel' to a new variable called 'slides'
Problem is when I do this is returns undefined and my thinking is the Axios query hasn't returned before this happens.
Is there a way to delay the axios request before the prop is passed and the child component always gets the expected response.
My code is below.
Parent
<template>
<div class='product-container'>
<home-carousel :carousel="carousel"></home-carousel>
<profiler></profiler>
<cta-sections :panels="panels"></cta-sections>
</div>
</template>
<script>
import api from '../api/Home'
import CtaSections from '../components/CtaSections'
import HomeCarousel from '../components/HomeCarousel'
import Profiler from '../components/Profiler'
export default {
components: {
CtaSections,
HomeCarousel,
Profiler,
},
data() {
return {
panels: [],
slides: 'test',
carouselPass: [],
carousel: [],
}
},
created() {
axios.get(window.SETTINGS.API_BASE_PATH + 'pages/5')
.then(response => {
this.panels = response.data.acf.split_panels;
this.carousel = response.data.acf.carousel;
this.carousel.forEach(function (item, index) {
if (index === 0) {
item.active = true;
item.opacity = 1;
} else {
item.active = false;
item.opacity = 0;
}
item.id = index
})
})
},
}
</script>
Child
<template>
<div class='slider'>
<transition-group class='carouse carousel--fullHeight carousel--gradient' tag="div" name="fade">
<div v-for="slide in slides"
class="carousel__slide"
v-bind:class="{ active: slide.active }"
:key="slide.id"
:style="{ 'background-image': 'url(' + slide.image.url + ')' }"
v-show="slide.active"
>
<div class="carousel__caption carousel__caption--centered">
<h2 class="heading heading--white heading--uppercase heading--fixed">{{ slide.tagline }}</h2>
</div>
</div>
</transition-group>
<div class='carousel__controls carousel__controls--numbered carousel__controls--white carousel__controls--bottomRight carousel__controls--flex'>
<div #click="next" class="in">
<img src="/static/img/svg/next-arrow.svg" />
<span v-if="carousel.length < 10">0</span>
<span>{{ slideCount }}</span>
<span>/</span>
<span v-if="carousel.length < 10">0</span>
<span>{{ carousel.length }}</span>
</div>
</div>
</div>
</template>
<script>
import bus from '../bus'
import Booking from './Booking'
export default {
name: 'HomeCarousel',
props: ['carousel'],
data() {
return {
slideCount: 1,
slides: [],
/*
slides: [{
image: this.themepath + 'home-banner.jpg',
active: true,
captionText: 'A PLACE AS UNIQUE AS YOU ARE',
buttonText: 'book now',
buttonUrl: '#',
opacity: 1,
id: 1
},
{
image: this.themepath + 'home-banner2.jpg',
active: false,
captionText: 'A PLACE AS UNIQUE AS YOU ARE',
buttonText: 'book now',
buttonUrl: '#',
opacity: 0,
id: 2
}
]
*/
}
},
methods: {
showBooking: function() {
this.$store.state.showBooking = true;
},
next() {
const first = this.slides.shift();
this.slides = this.slides.concat(first)
first.active = false;
this.slides[0].active = true;
if (this.slideCount === this.slides.length) {
this.slideCount = 1;
} else {
this.slideCount++;
}
},
previous() {
const last = this.slides.pop()
this.slides = [last].concat(this.slides)
// Loop through Array and set all active values to false;
var slideLength = this.slides.length;
for (var slide = 0; slide < slideLength; slide++) {
this.slides[slide].active = false;
}
// Apply active class to first slide
this.slides[0].active = true;
this.slideCount--;
},
loopInterval() {
let self = this;
setInterval(function () {
self.next()
}, 8000);
}
},
created() {
this.slides = this.carousel;
}
}
</script>
You can just watch the prop and set this.slides when it changes, i.e. when the async call has finished:
watch:{
carousel(value) {
this.slides = value
}
}
Here's a JSFiddle: https://jsfiddle.net/nwLh0d4w/

Vue.js data value not updating when adding new value

When new bars value are set, the data is not updated. Please check below;
I'm planning to do dynamic bars and buttons. Once the buttons are clicked, the new values will be updated to bars values.
Here is the link
HTML
<h1><span>Vue.js</span> Progress Bar</h1>
<div id="app">
<div v-for="(index, bar) in bars" class="shell">
<div class="bar" :style="{ width: bar + '%' }" .>
<span>{{ bar }}%</span>
<input type="radio" id="radio-bar" value="{{ index }}" v-model="picked" v-on:change="set(index)">
</div>
</div>
<span v-for="(index, button) in buttons">
<button value="{{ button }}" #click.prevent="makeProgress(button)">{{ button }}</button>
</span>
<br>
<p>Selected Bar: {{ picked }}</p>
<p>Button Value: {{ buttonVal }}</p>
</div>
JS
var dataURL = 'http://pb-api.herokuapp.com/bars';
var vm = new Vue({
el: "#app",
data: {
maxColor: "#F22613",
bars: [],
buttons: [],
limit: 0,
selectedBar: 0,
buttonVal: 0
},
methods: {
fetchData: function(params) {
this.$http.get(dataURL, function(data) {
this.$set('bars', data.bars);
this.$set('buttons', data.buttons);
this.$set('limit', data.limit);
console.log(params);
});
},
set: function(index) {
this.selectedBar = index;
},
makeProgress: function(button) {
var self = this;
self.buttonVal += button;
this.reachMax();
Vue.set(this.bars, 0, 55);
},
reachMax() {
if(this.buttonVal >= this.limit){
alert('Reached Limit' + this.limit)
}
}
},
created: function() {
this.fetchData();
}
});