How do I create a simple 10 seconds countdown in Vue.js - javascript

I want to do a simple countdown from 10 to 0
I found solution online using normal javascript but let say I want to do it in Vue . The solution in Jquery
Create a simple 10 second countdown
<template>
{{ countDown }}
</template>
<script>
export default {
computed: {
countDown() {
// How do i do the simple countdown here?
}
}
}
</script>
How do I recreate the same functionality in Vue.js?
Thanks

Whilst the accepted answer works, and is great, it can actually be achieved in a slightly simpler way by utilising Vue.js watchers:
<template>
{{ timerCount }}
</template>
<script>
export default {
data() {
return {
timerCount: 30
}
},
watch: {
timerCount: {
handler(value) {
if (value > 0) {
setTimeout(() => {
this.timerCount--;
}, 1000);
}
},
immediate: true // This ensures the watcher is triggered upon creation
}
}
}
</script>
The benefit of using this method is that the timer can be immediately reset by simply setting the value of timerCount.
If you would like to play/pause the timer, then you can achieve this like so (note - this is not a perfect solution as it will round to the nearest second):
<template>
{{ timerCount }}
</template>
<script>
export default {
data() {
return {
timerEnabled: true,
timerCount: 30
}
},
watch: {
timerEnabled(value) {
if (value) {
setTimeout(() => {
this.timerCount--;
}, 1000);
}
},
timerCount: {
handler(value) {
if (value > 0 && this.timerEnabled) {
setTimeout(() => {
this.timerCount--;
}, 1000);
}
},
immediate: true // This ensures the watcher is triggered upon creation
}
}
methods: {
play() {
this.timerEnabled = true;
},
pause() {
this.timerEnabled = false;
}
}
}
</script>

Please check if this works for you.
<template>
{{ countDown }}
</template>
<script>
export default {
data () {
return {
countDown: 10
}
},
methods: {
countDownTimer () {
if (this.countDown > 0) {
setTimeout(() => {
this.countDown -= 1
this.countDownTimer()
}, 1000)
}
}
},
created () {
this.countDownTimer()
}
}
</script>

Here is a component I made for a countdown timer :
<template>
<div>
<slot :hour="hour" :min="min" :sec="sec"></slot>
</div>
</template>
<script>
export default {
props : {
endDate : { // pass date object till when you want to run the timer
type : Date,
default(){
return new Date()
}
},
negative : { // optional, should countdown after 0 to negative
type : Boolean,
default : false
}
},
data(){
return{
now : new Date(),
timer : null
}
},
computed:{
hour(){
let h = Math.trunc((this.endDate - this.now) / 1000 / 3600);
return h>9?h:'0'+h;
},
min(){
let m = Math.trunc((this.endDate - this.now) / 1000 / 60) % 60;
return m>9?m:'0'+m;
},
sec(){
let s = Math.trunc((this.endDate - this.now)/1000) % 60
return s>9?s:'0'+s;
}
},
watch : {
endDate : {
immediate : true,
handler(newVal){
if(this.timer){
clearInterval(this.timer)
}
this.timer = setInterval(()=>{
this.now = new Date()
if(this.negative)
return
if(this.now > newVal){
this.now = newVal
this.$emit('endTime')
clearInterval(this.timer)
}
}, 1000)
}
}
},
beforeDestroy(){
clearInterval(this.timer)
}
}
</script>

Make it a component so you can re-use it.
<body>
<div id="app">
<counter></counter>
<counter></counter>
<counter></counter>
</div>
<script>
Vue.component('counter', {
template: '<button v-on:click="countDownTimer()">{{ countDown }}</button>',
data: function () {
return {
countDown: 10,
countDownTimer() {
if (this.countDown > 0) {
setTimeout(() => {
this.countDown -= 1
this.countDownTimer();
}, 1000)
}
}
}
}
})
const app = new Vue({
el: '#app'
})
</script>
</body>

In case if anyone uses Luxon's DateTime object instead of native JS's Date object.
<template>
<span v-if="timer">
{{ timeCalculated }}
</span>
</template>
<script>
import { DateTime } from 'luxon'
export default {
name: 'CountDownTimer',
props: {
endDate: {
type: String,
required: true
}
},
data () {
return {
now: DateTime.local(),
timer: null
}
},
computed: {
timeCalculated () {
const endDateDateTimeObj = DateTime.fromISO(this.endDate)
const theDiff = endDateDateTimeObj.diff(this.now, ['hours', 'minutes', 'seconds'])
return `${theDiff.hours}:${theDiff.minutes}:${Math.round(theDiff.seconds)}`
}
},
watch: {
endDate: {
immediate: true,
handler (endDateTimeStr) {
const endDateTimeObj = DateTime.fromISO(endDateTimeStr)
if (this.timer) {
clearInterval(this.timer)
}
this.timer = setInterval(() => {
this.now = DateTime.local()
if (this.now > endDateTimeObj) {
this.now = endDateTimeObj
clearInterval(this.timer)
}
}, 1000)
}
}
},
beforeDestroy () {
clearInterval(this.timer)
}
}
</script>
In my case endDate has String type because the value is restored from JSON. You can easily change it to the original DateTime object.

Use dates.
<template>
<div>{{ time }}</div>
</template>
<script>
export default {
name: 'Timer',
props: ['seconds'],
data: () => ({
interval: undefined,
end: new Date(0, 0, 0),
current: new Date(0, 0, 0, 0, 0, this.seconds)
}),
computed: {
time: {
get() {
return this.current.getSeconds();
},
set(d) {
this.current = new Date(0, 0, 0, 0, 0, this.current.getSeconds() + d);
}
}
},
methods: {
countDown() {
this.end >= this.current
? clearInterval(this.interval)
: (this.time = -1);
}
},
created() {
this.interval = setInterval(this.countDown, 1000);
}
};
</script>

Related

Vue: timer value doesn't update when using setInterval

I have the following code for updating the number of seconds elapsed and displaying it:
<template>
<div>
{{timerValue}}
</div>
</template>
<script>
export default {
name: "App",
components: {
},
data() {
return {
timerValue: ""
}
},
created() {
let seconds = 0;
this.timerValue = seconds;
setInterval(function() {
seconds++;
})
}
};
</script>
However the page always displays
0
What am I doing wrong?
https://codesandbox.io/s/still-cache-1mdgr6?file=/src/App.vue
Maybe like following snippet:
const app = Vue.createApp({
data() {
return {
timerValue: 0 // set value default is 0
}
},
created() {
setInterval(() => {
this.timerValue++;
}, 1000)
}
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div>
{{timerValue}}
</div>
</div>
You shuold increment this.timerValue instead seconds
created() {
let seconds = 0;
this.timerValue = seconds;
setInterval(function() {
this.timerValue++;
}.bind(this))
}
Or with arrow functions
created() {
let seconds = 0;
this.timerValue = seconds;
setInterval(() => {
this.timerValue++;
})
}
I have 3 points for you to pay attention to
In the setInterval function you only change the seconds variable, not this.timerValue at all. So page always shows 0.
The setInterval function doesn't have interval argument yet, so it won't run every second.
Also the initialization value is not clear, I think you should understand the logic of the program and spent a little time for understand what you written.
<template>
<div>
{{timerValue}}
</div>
</template>
<script>
export default {
name: "App",
components: {
},
data() {
return {
timerValue: 0,
}
},
mounted() { // use mounted much better than created in this case
setInterval(() => {
this.timerValue++;
}, 1000)
}
};
</script>
refs: https://www.w3schools.com/jsref/met_win_setinterval.asp
that's why i always tell people to learn language logic or at least javascript before learning something else
you need to update the react value because javascript doesn't have an api that allows memory cell assignment
<script>
export default {
name: "App",
components: {
},
data() {
return {
timerValue: 0 // set default value is number
}
},
created() {
setInterval(() => {
this.timerValue++;
}, 1_000 /* delay 1s */)
}
};
</script>
I found two things wrong about my code:
Using this inside a function(). An arrow function should be used, like this:
setInterval(() => {}
this.timerValue = seconds is not enough to make Vue update timerValue when seconds is updated. Change of seconds doesn't trigger change of timerValue.
It can be solved by using computed property:
computed: {
timerValue: function() {
return this.seconds;
}
},
So the whole code would look like this:
https://codesandbox.io/s/festive-cannon-8qvpn9?file=/src/App.vue
<template>
<div>
{{ timerValue }}
</div>
</template>
<script>
export default {
name: "App",
components: {},
data() {
return {
seconds: 0,
};
},
created() {
setInterval(() => {
this.seconds++;
}, 1000);
},
computed: {
timerValue: function () {
return this.seconds;
},
},
};
</script>

I have a clock ticking when click start and stop made by ReactJS

I have a timer that work authomatically.But I want to add new feature to it. When I click it it should stop,when I click second time it should work.I used componentWillMount to create it.But it have some problems that I can not solve.
import React from 'react';
import ReactDOM from 'react-dom';
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date(), status: true };
this.handleStatus = this.handleStatus.bind(this);
}
componentDidMount() {
if (this.state.status) {
const oneSecond = 1000;
this.clear = setInterval(() => {
this.setState({ date: new Date() });
}, oneSecond);
}
}
componentWillMount() {
if (!this.state.status) {
clearInterval(this.clear);
}
}
handleStatus() {
this.setState({ status: !this.state.status });
}
render() {
console.log(this.state.status);
return (
<div className="fs-5 fw-bold text-primary">
{this.state.date.toLocaleTimeString()}
<br />
<button className="btn btn-warning" onClick={this.handleStatus}>
Tikla
</button>
</div>
);
}
}
export default Clock;
Try like this.
componentDidMount() {
if (this.state.status) {
const oneSecond = 1000;
this.clear = setInterval(() => {
this.setState({ date: new Date() });
}, oneSecond);
}
}
handleStatus() {
if(this.state.status){
clearInterval(this.clear);
}
else{
const oneSecond = 1000;
this.clear = setInterval(() => {
this.setState({ date: new Date() });
}, oneSecond);
}
this.setState({ status: !this.state.status });
}
You can confrim here.
https://codesandbox.io/s/youthful-cdn-lpntx?file=/src/App.js

How to implement global variable in react functional components

I am new to react and decided to practice by implementing a simple stop watch using both class and functional components.
I successfully implemented the stop watch using a class component. Below is the code:
Class Component
class Stopwatch extends Component {
state = {
status: false,
ms: 0,
seconds: 0,
minutes: 0,
};
stopms;
stopSeconds;
stopMinutes;
handleClick = () => {
this.changeStatus();
if (this.state.status) {
clearInterval(this.stopms);
clearInterval(this.stopSeconds);
clearInterval(this.stopMinutes);
} else {
this.stopms = setInterval(this.changeMs, 1);
this.stopSeconds = setInterval(this.changeSeconds, 1000);
this.stopMinutes = setInterval(this.changeMinutes, 60000);
}
};
changeStatus = () => {
return this.setState((state) => {
return { status: !state.status };
});
};
changeMs = () => {
return this.setState((state) => {
if (state.ms === 99) {
return { ms: 0 };
} else {
return { ms: state.ms + 1 };
}
});
};
changeSeconds = () => {
return this.setState((state) => {
if (state.seconds === 59) {
return { seconds: 0 };
} else {
return { seconds: state.seconds + 1 };
}
});
};
changeMinutes = () => {
return this.setState((state) => {
if (state.seconds === 59) {
return { minutes: 0 };
} else {
return { minutes: state.minutes + 1 };
}
});
};
handleReset = () => {
clearInterval(this.stopms);
clearInterval(this.stopSeconds);
clearInterval(this.stopMinutes);
this.setState({ seconds: 0, status: false, minutes: 0, ms: 0 });
};
componentWillUnmount() {
clearInterval(this.stopms);
clearInterval(this.stopSeconds);
clearInterval(this.stopMinutes);
}
render() {
return (
<div>
<h1>
{this.state.minutes} : {this.state.seconds} .{" "}
<span>{this.state.ms}</span>
</h1>
<button className="btn btn-lg btn-dark" onClick={this.handleClick}>
{this.state.status === false ? "Start" : "Pause"}
</button>
<button className="btn btn-lg btn-dark" onClick={this.handleReset}>
Reset
</button>
</div>
);
}
}
export default Stopwatch;
Now I'm trying to implement the same code above but using a functional component as shown below:
Functional Component
function Stopwatch() {
const [timeState, setTimeState] = useState({
status: false,
ms: 0,
seconds: 0,
minutes: 0,
});
let stopms;
let stopSeconds;
let stopMinutes;
const handleClick = () => {
changeStatus();
if (timeState.status) {
clearInterval(stopms);
clearInterval(stopSeconds);
clearInterval(stopMinutes);
} else {
stopms = setInterval(changeMs, 1);
stopSeconds = setInterval(changeSeconds, 1000);
stopMinutes = setInterval(changeMinutes, 60000);
}
};
const changeStatus = () => {
return setTimeState((prevState) => {
return { ...prevState, status: !prevState.status };
});
};
const changeMs = () => {
return setTimeState((prevState) => {
if (prevState.ms === 99) {
return { ...prevState, ms: 0 };
} else {
return { ...prevState, ms: prevState.ms + 1 };
}
});
};
const changeSeconds = () => {
return setTimeState((prevState) => {
if (prevState.seconds === 59) {
return { ...prevState, seconds: 0 };
} else {
return { ...prevState, seconds: prevState.seconds + 1 };
}
});
};
const changeMinutes = () => {
return setTimeState((prevState) => {
if (prevState.seconds === 59) {
return { ...prevState, minutes: 0 };
} else {
return { ...prevState, minutes: prevState.minutes + 1 };
}
});
};
const handleReset = () => {
clearInterval(stopms);
clearInterval(stopSeconds);
clearInterval(stopMinutes);
setTimeState({ seconds: 0, status: false, minutes: 0, ms: 0 });
};
return (
<div>
<h1>
{timeState.minutes} : {timeState.seconds} . <span>{timeState.ms}</span>
</h1>
<button className="btn btn-lg btn-dark" onClick={handleClick}>
{timeState.status === false ? "Start" : "Stop"}
</button>
<button className="btn btn-lg btn-dark" onClick={handleReset}>
Reset
</button>
</div>
);
}
export default Stopwatch;
The Problem
In the class component, I implemented the "Pause" functionality using the handleClick function which calls clearInterval with it's argument as the global variables stopms, stopSeconds, stopMinutes that I declared initially. This worked just fine because these global variables were holding values returned from the respective setInterval when the stop watch started counting.
Now in the functional component, I replicated the same logic by declaring the same global variables using the "let" keyword. But the "Pause" functionality is not working. When the "Start" button hit and the handleClick function called, the setIntervals were called and their return values were stored in the respective global variables. But when the "Pause" button was hit, all the global variables had "undefined" as their values.
Please I would like to know if there's any other way I can declare global variables and use them to hold values throughout a component's life cycle asides using state.
Functional components are executed from top to bottom whenever state changes, so the whole function is re-executed and that's how it returns the new JSX, compare this to class components where only render() function is executed on render, that's how functional components work.
The problem is that your global variables are in fact not global and a part of the function, hence they are re-initialized each time render is happening.
Two ways to solve this
Move your variables to the state
function Stopwatch() {
const [timeState, setTimeState] = useState({
status: false,
ms: 0,
seconds: 0,
minutes: 0,
stopms : null,
stopSeconds : null,
stopMinutes: null,
});
const handleClick = () => {
changeStatus();
if (timeState.status) {
clearInterval(timeState.stopms);
clearInterval(timeState.stopSeconds);
clearInterval(timeState.stopMinutes);
} else {
let stopms = setInterval(changeMs, 1);
let stopSeconds = setInterval(changeSeconds, 1000);
let stopMinutes = setInterval(changeMinutes, 60000);
setTimeState(prev => ({..prev, stopms, stopSeconds, stopMinutes})); // update the values in state
}
};
......
const handleReset = () => {
clearInterval(timeState.stopms); // use the same values to clear them
clearInterval(timeState.stopSeconds);
clearInterval(timeState.stopMinutes);
.....
};
.....
}
Or make them global by placing them outside of your component, Will work but not recommended.
In your component file.
// declare them just above your function
let stopms;
let stopSeconds;
let stopMinutes;
function Stopwatch() {
const [timeState, setTimeState] = useState({
status: false,
ms: 0,
seconds: 0,
minutes: 0,
});
.....
const handleClick = () => {
changeStatus();
if (timeState.status) {
clearInterval(stopms);
clearInterval(stopSeconds);
clearInterval(stopMinutes);
} else {
stopms = setInterval(changeMs, 1);
stopSeconds = setInterval(changeSeconds, 1000);
stopMinutes = setInterval(changeMinutes, 60000);
}
.......
};

Component in use in multiple places all are being updated at the same time

I am new still to Vue I have a component that is used to accept and display files when they are dragged & Dropped onto the component. According to the documentation if I have my attributes in a data function then I can use this component in multiple places and have them update independently of the other component.
This is not the case when I test the component in my add screen the component in the view screen is still updated with the same data. I cannot figure out why this is happening so was wondering if I could get some fresh eyes
to take a look or point me in the right direction on how I can resolve this issue.
this is my ImageViewer code
<template>
<div id="image-panel"
#dragenter="allowDrag"
#mouseenter="toggleSingleViewActionbar('show')"
#mouseleave="toggleSingleViewActionbar('hide')">
<div id="dragDropOverlay" class="drop display-inline align-center"
:class="[isOverlay ? 'overlay' : '']"
#dragleave="retainOverlay"
#dragover.prevent
#drop="onDrop">
<div class="overlay-text">Drag files here</div>
</div>
<single-view v-if="display === 'single'"
:file-list-length="this.fileList.length === 0 ? 0 : this.fileList.length - 1"
:current-position="currentPosition"
:display-type="displayType"
v-on:addToFileList="updateFileList"/>
<grid-view v-else
:file-list-length="this.fileList.length === 0 ? 0 : this.fileList.length - 1"
:current-position="currentPosition"/>
<app-image-section-single-view
v-if="display === 'single'"
:fileList="fileList"
:currentPosition="currentPosition"
:is-overlay="isOverlay"
/>
<app-image-section-grid-view v-else :file-list="fileList"/>
<snack-bar v-if="deleteConfirmation" message="Are you sure you want to delete this file?"></snack-bar>
</div>
</template>
<script>
import ImageSectionSingleView from './ImageSectionSingleView'
import ImageSectionGridView from './ImageSectionGridView.vue'
import { eventBus } from '#/main'
import Singleview from '../Actionbar/Singleview'
import Gridview from '../Actionbar/Gridview'
import SnackBar from '../SnackBar/SnackBar'
import { states } from '../enums/enums'
export default {
data () {
return {
fileList: [],
currentPosition: 0,
display: 'single',
displayType: '',
isOverlay: false,
deleteConfirmation: false
}
},
watch: {
fileList () {
eventBus.$emit('updateFileList', this.fileList)
}
},
methods: {
onDrop (e) {
e.stopPropagation()
e.preventDefault()
if (!this.isOverlay) return
this.updateFileList(e.dataTransfer.files)
this.isOverlay = false
},
allowDrag () {
this.isOverlay = this.$store.getters.appState !== states.view
},
retainOverlay (event) {
if (!this.isOverlay) return
this.isOverlay = !!event.relatedTarget.closest('#dragDropOverlay')
},
getFileList () {
return this.$store.getters.getFileList
},
updateFileList (files) {
this.fileList.push.apply(this.fileList, (
[...files].map((f) => ({
name: f.name,
data: URL.createObjectURL(f),
type: f.type
}))))
this.currentPosition = this.fileList.length - 1
},
getCurrentPosition () {
return this.$store.getters.getCurrentPosition
},
// updateCurrentPosition (position) {
// this.$store.commit('updateCurrentPosition', position)
// },
toggleSingleViewActionbar (action = '') {
this.displayType = action
},
deleteImage (index = -1) {
if (index === -1) {
index = this.currentPosition
}
this.fileList.splice(index, 1)
if (index < this.fileList.length) return
if (this.currentPosition > 0) {
this.currentPosition--
}
},
deleteSelected (indexes) {
for (let i = 0; i < indexes.length; i++) {
this.deleteImage(indexes[i])
}
this.fileList.map((file) => {
file.isVisible = false
})
}
},
created () {
this.fileList = this.getFileList()
this.currentPosition = this.getCurrentPosition()
eventBus.$on('deleteSelectedFiles', (indexes) => {
this.deleteSelected(indexes.sort().reverse())
if (this.fileList.length === 0) {
this.currentPosition = 0
this.display = 'single'
}
})
eventBus.$on('setSelectedFiles', (state) => {
this.fileList.map((file) => {
file.isVisible = state
})
})
eventBus.$on('moveToNextFile', (positionNext) => {
this.currentPosition = positionNext++
})
eventBus.$on('moveToPreviousFile', (positionPrevious) => {
this.currentPosition = positionPrevious--
})
eventBus.$on('confirmDelete', () => {
eventBus.$emit('singleDeleteConfirmation', () => {
})
})
eventBus.$on('confirmationYes', () => {
this.deleteImage()
eventBus.$emit('singleDeleteSnackbarClose')
})
eventBus.$on('confirmationNo', () => {
eventBus.$emit('singleDeleteSnackbarClose')
})
eventBus.$on('switchView', (value) => {
this.display = value
})
eventBus.$on('singleDeleteConfirmation', () => {
this.deleteConfirmation = !this.deleteConfirmation
})
eventBus.$on('singleDeleteSnackbarClose', () => {
this.deleteConfirmation = false
})
},
components: {
appImageSectionSingleView: ImageSectionSingleView,
appImageSectionGridView: ImageSectionGridView,
singleView: Singleview,
gridView: Gridview,
SnackBar: SnackBar
}
}
</script>
and this is where the image/file is displayed
<template>
<div class="display-inline">
<img #dragenter="isOverlay=true" v-if="fileList.length > 0" :src="fileList[currentPosition].data" class="img" />
<img v-else src="../../../src/assets/logo.png" class="img" />
</div>
</template>
<script>
export default {
props: {
fileList: Array,
currentPosition: Number,
fileListLength: Number,
isOverlay: Boolean
}
}
</script>
How can I get it so that It is independently displaying in each of my sections where the component is called ?
Initially the data properties are all independent but then you're assigning this.fileList = this.getFileList(), which is grabbing an array from the store. All components will be sharing that same array in their fileList property. - Comment by skirtle

vue2 disable input with multiple components

Vue 2 - disable input - multiple components
Hi all,
I struggle to solve a problem, where I want to disable other input fields, once the first char has been entered in another input field.
I've been trying to solve this with $emit, #focus, and other solutions and I'm still stuck. I was also not able to utilize the answers to be found here.
Snippet:
const Autocomplete = {
name: "autocomplete",
props: {
items: {
type: Array,
required: false,
default: () => ['test']
},
isAsync: {
type: Boolean,
required: false,
default: false
},
formLock: {
type: Boolean,
},
formId: {
type: String,
}
},
data() {
return {
isOpen: false,
results: [],
search: "",
isLoading: false,
arrowCounter: 0,
};
},
methods: {
onChange() {
// Let's warn the parent that a change was made
this.$emit("input", this.search);
// Is the data given by an outside ajax request?
if (this.isAsync) {
this.isLoading = true;
} else {
// Let's search our flat array
this.filterResults();
this.isOpen = true;
}
if (this.search.length === 0) {
this.isOpen = false;
}
console.log(this.search.length);
},
disableOther() {
var searchForms = document.getElementsByClassName('searchForm');
for (i = 0; i < searchForms.length; i++) {
}
console.log(searchForms.length);
},
filterResults() {
// first uncapitalize all the things
this.results = this.items.filter(item => {
return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1;
});
},
setResult(result) {
this.search = result;
this.isOpen = false;
},
onArrowDown(evt) {
if (this.arrowCounter < this.results.length) {
this.arrowCounter = this.arrowCounter + 1;
}
},
onArrowUp() {
if (this.arrowCounter > 0) {
this.arrowCounter = this.arrowCounter - 1;
}
},
onEnter() {
this.search = this.results[this.arrowCounter];
this.isOpen = false;
this.arrowCounter = -1;
},
handleClickOutside(evt) {
if (!this.$el.contains(evt.target)) {
this.isOpen = false;
this.arrowCounter = -1;
}
}
},
mounted() {
document.addEventListener("click", this.handleClickOutside);
},
destroyed() {
document.removeEventListener("click", this.handleClickOutside);
},
template: `
<div>
<input type="text" #input="onChange" class="searchForm" v-model="search" #keyup.down="onArrowDown" #keyup.up="onArrowUp" #keyup.enter="onEnter" v-bind:disabled="formLock" #focus="disableOther" />
<ul id="autocomplete-results" v-show="isOpen" class="autocomplete-results">
<li class="loading" v-if="isLoading">
Loading results...
</li>
<li v-else v-for="(result, i) in results" :key="i" #click="setResult(result)" class="autocomplete-result" :class="{ 'is-active': i === arrowCounter }">
{{ result }}
</li>
</ul>
</div>
`,
};
new Vue({
el: "#productSearchApp",
name: "productSearchApp",
data() {
return {
productName: [],
productCatalog: [],
lock: false,
searchName: "searchForm",
searchCatalog: "searchCatalog"
}
},
mounted() {
fetch("http://cormay.314-work.pl/wp-json/wp/v2/product")
.then(response => response.json())
.then((data) => {
for (i = 0; i < data.length; i++) {
this.productName.push(data[i].title.rendered);
};
for (i = 0; i < data.length; i++) {
this.productCatalog.push(data[i].product_catalog);
};
})
},
components: {
autocomplete: Autocomplete,
},
methods: {
updateLock(updateLock) {
this.lock = updateLock;
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="productSearchApp">
<autocomplete :items="productName" :form-id="searchName"></autocomplete>
<autocomplete :items="productCatalog" :form-id="searchCatalog"></autocomplete>
</div>
Thanks!
You could try something like this.
You'll notice that I'm passing the name of model back and forth a bit, which may seem like a nuisance to manage, but if you were to configure it as part of a v-for loop, it would make it easy to manage.
Vue.component('custom-input', {
props: ['value', 'disabled'],
template: `
<input
:disabled="disabled"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
new Vue({
el:'#app',
data:{
first: null,
second: null,
active: null
},
methods: {
onChange(e, model){
this.active = null
if (e.length > 0) {
this.active = model
}
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.js"></script>
<div id="app">
<custom-input v-model="first" :disabled="active !== null && active !== 'first'" #input="onChange($event, 'first')">Foo</custom-input>
<custom-input v-model="second" :disabled="active !== null && active !== 'second'" #input="onChange($event, 'second')">Bar</custom-input>
</div>
Maybe you could use a state manager like VueX or Instance Properties
... like:
// global state
Vue.prototype.$state = new Vue({
data: { 
active: null
}
})
// input component
Vue.component('vue-input', {
props: ['value'],
template: `
<label>
<slot></slot>
<input v-model="model" :disabled="disabled" />
</label>
`,
data() {
return {
// avoid mutation
model: this.value
}
},
beforeDestroy() {
// reset state in case we remove the active component
if (this.$state.active === this) this.$state.active = null;
},
watch: {
model(value) {
// set active to 'this' if value length > 0 or null active value
this.$state.active = value.length ? this : null;
}
},
computed: {
disabled() {
// disable if active is not null and not equal to 'this'
return this.$state.active && this.$state.active !== this;
}
}
})
// app instance
new Vue({
el: '#app',
data: {
foo: null,
bar: null
}
})
label {
font: caption;
display: flex;
justify-content: space-between;
width: 160px;
margin-bottom: 10px;
}
input:disabled {
background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.js"></script>
<div id="app">
<vue-input v-model="foo">Foo</vue-input>
<vue-input v-model="bar">Bar</vue-input>
</div>

Categories