Awaiting till user finishes writing to input field in Vue.js - javascript

I have a QR code creating page. I want my QR codes to be created dynamically by user input. But I don't want to instantly create a QR code. I want to wait my user to finish writing then after one second i will generate the QR code. So I have a template like below:
<div class="app">
<qrcode-vue :value="genaratedQrCode"></qrcode-vue>
<input type="text" v-model="qrCodeInput" />
</div>
And my script:
import QrcodeVue from 'qrcode.vue';
export default {
data() {
return {
genaratedQrCode: '',
qrCodeInput: '',
isInputFunctionRunning: false
}
},
watch: {
async qrCodeInput() {
if (this.isInputFunctionRunning) {
return;
}
this.isInputFunctionRunning = true;
await new Promise(r => setTimeout(r, 1000));
this.genaratedQrCode = this.qrCodeInput;
this.isInputFunctionRunning = false;
}
}
components: {
QrcodeVue,
},
}
Apparently the code is not working. It generated the QR code every one seconds. What I want is waiting till user finished, then updating after 1 seconds.

You have to use .lazy modifier :
<input type="text" v-model.lazy="qrCodeInput" />
If you want to wait some delay try this :
import QrcodeVue from 'qrcode.vue';
function debounce (fn, delay) {
var timeoutID = null
return function () {
clearTimeout(timeoutID)
var args = arguments
var that = this
timeoutID = setTimeout(function () {
fn.apply(that, args)
}, delay)
}
}
export default {
data() {
return {
genaratedQrCode: '',
qrCodeInput: '',
isInputFunctionRunning: false
}
},
watch: {
qrCodeInput:debounce(function() {
if (this.isInputFunctionRunning) {
return;
}
this.isInputFunctionRunning = true;
this.genaratedQrCode = this.qrCodeInput;
this.isInputFunctionRunning = false;
},1000)
}
components: {
QrcodeVue,
},
}
This is based on this answer;

Related

Don't call popup if another one is already on the page

There is a popup (1) that should be called after 15 seconds of being on the page.
But if the user opened some other popup(2), then don't call the first one.
popup(1);
mounted() {
this.openModal();
},
// methods
openModal() {
setTimeout(() => {
this.isModalVisible = true;
}, 15000);
},
How to do it?
Perhaps need to stop setTimeOut?
Maybe something like following snippet:
new Vue({
el: '#demo',
data() {
return {
isModalVisible: false,
isModalOther: false
}
},
methods: {
openModal() {
setTimeout(() => {
if(!this.isModalOther) this.isModalVisible = true;
}, 5000);
},
openOtherModal() {
this.isModalVisible = false
this.isModalOther = true;
},
},
mounted() {
this.openModal();
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div v-if="isModalVisible">popup1</div>
<div v-if="isModalOther">popup2</div>
<button #click="openOtherModal">open popup2</button>
</div>
To cancel a timeout, all you need to do is call clearTimeout(TimeoutID);. A timeoutID is a returned by the setTimeout() method automatically, so just save it in a variable
let timer = setTimeout(...);
then, when you call popup(2), just add
this.clearTimeout(timer);
and the first popup won't show

Running asynchronous setTimeout counter parallely to Promise.all() to display a progress loader in %

I am having an issue running an asynchronous setTimeout counter parallel to a Promise.all() handler when trying to display a progress loader in %.
Here the details:
I've built a Vue app consisting of three components.
The first component "Test.vue" is importing a progress bar component and a pivot component which is containing about 12 pivot tables with data from different Google firestore collections (handled via Promise.all()).
Currently, it takes about 15 seconds until the pivot component is rendered successfully.
During this waiting period, I want to show a progress bar in % which goes up to 100% until all data for the pivot component is loaded and rendered successfully.
I was trying different approaches and currently, I am following the "easy" approach that the progress bar just shall display within 15 seconds the loading progress.
But even this approach doesn't work properly for me.
It seems to me like the progress functionality always waits until the loading and rendering of the pivot component is finished.
I really don't have any idea anymore how to solve this.
Which recommendations do you have?
Hint (if important): Inside the pivot component the data is loaded inside the mounted-hook via Promise.all()
Here the code for the Test.vue component:
Test.vue:
<template>
<mdb-container class="full-width" style="margin-top:35px !important;">
<mdb-row v-if="tabChange">
<mdb-progress :height="30" :value="value">{{value}} %</mdb-progress>
</mdb-row>
<mdb-card>
<mdb-card-header>
<mdb-tab default class="card-header-tabs">
<mdb-tab-item
v-for="tab in tabs"
v-bind:key="tab"
v-bind:class="[{ active: currentTab === tab }]"
:active="currentTab == tab"
#click.native.prevent="currentTab=tab"
>{{ tab }}</mdb-tab-item>
</mdb-tab>
</mdb-card-header>
<mdb-card-body>
<mdb-tab-content>
<mdb-tab-pane class="fade">
<mdb-row>
<keep-alive>
<component v-bind:is="currentTabComponent" v-on:finished="setFinished" class="tab"></component>
</keep-alive>
</mdb-row>
</mdb-tab-pane>
</mdb-tab-content>
</mdb-card-body>
</mdb-card>
</mdb-container>
</template>
<script>
/* eslint-disable */
import { mdbProgress, mdbContainer, mdbRow, mdbCol, mdbBtn, mdbCard, mdbCardTitle, mdbCardText, mdbCardFooter, mdbCardBody, mdbCardHeader, mdbListGroup, mdbListGroupItem, mdbNavItem, mdbCardGroup, mdbIcon, mdbFooter, mdbTab, mdbTabItem, mdbTabContent, mdbTabPane } from 'mdbvue';
import { db } from '#/main'
import PivotTable from '#/components/flight-builder/PivotTable'
import DefaultFilters from '#/components/flight-builder/filters/DefaultFilters'
import MainTab from '#/components/flight-builder/ads-archive/MainTab'
import Channel from '#/components/flight-builder/ads-archive/Channel'
import Loader from '#/components/Loader'
//import excel from "vue-excel-export"
/*const Channel = () => ({
component: import('#/components/flight-builder/ads-archive/Channel'),
loading: LoadingComponent,
error: LoadingComponent,
delay: 200,
timeout: 3000
})*/
export default {
name: 'AdsArchivePage',
components: {
PivotTable,
mdbContainer,
mdbRow,
mdbCol,
mdbBtn,
mdbCard,
mdbCardTitle,
mdbCardText,
mdbCardFooter,
mdbCardBody,
mdbCardHeader,
mdbListGroup,
mdbListGroupItem,
mdbNavItem,
mdbCardGroup,
mdbIcon,
mdbFooter,
mdbTab,
mdbTabItem,
mdbTabContent,
mdbTabPane,
mdbProgress,
DefaultFilters,
MainTab,
Channel,
Loader
},
data: () => {
return {
active: 0,
currentTab: "Main Tab",
value: 0,
tabs: ["Main Tab", "Channel", "Flight", "AdType", "Creative", "Spot length"],
componentMatcher: {
"Main Tab": "",
"Channel": Channel,
"Flight": "",
"AdType": "",
"Creative": "",
"Spot length": ""
},
finishedLoading: false
}
},
methods: {
setFinished(finishedLoading) {
this.finishedLoading = finishedLoading
},
timeout(ms) { //pass a time in milliseconds to this function
return new Promise(resolve => setTimeout(resolve, ms));
},
async wait() {
let loadingTime = 15
this.value = 0
console.log("wait123")
for (let i = 0; i<=loadingTime; i++) {
//setTimeout(() => {this.value = Math.round((i / loadingTime)*100)}, 15000);
this.value = Math.round((i / loadingTime)*100)
this.$forceUpdate()
await this.timeout(1000)
//await sleep(1000);
}
}
},
computed: {
currentTabComponent: function() {
return this.componentMatcher[this.currentTab]
},
tabChange: function() {
if (this.prevTab != this.currentTab) {
this.wait()
return true
}
return false
}
}
}
</script>
During this waiting period I want to show a progress bar in % which
goes up to 100% until all data for the pivot component is loaded and
rendered successfully.
I can tell you that the #Keith's answer did the trick like below to show all promises progress:
const allProgress = (proms, progress_cb) => {
let d = 0;
progress_cb(0); // Start progress_cb
for (const p of proms) { // Interate all promises
p.then(() => {
d++;
progress_cb( (d * 100) / proms.length ); // Display each item when it's done.
});
}
return Promise.all(proms);
}
const test = ms => new Promise((resolve) => {setTimeout(() => resolve(), ms);});
allProgress([test(1000), test(2000), test(3000)],
p => console.log(`% Done = ${p.toFixed(2)}`));
As a result, you can adjust the progress_cb method based on your needs (Instead of just console.log)
Updated
The second solution is to use promiss.all. Basically, this one the same as naortor's answer but refactor code.
var count = 0;
const waitThenTrigger = p => p.then(val => { progress(++count); return val;});
const progress = count => console.log(`% Done = ${(count*100/promiseArray.length).toFixed(2)}`);
const createPromise = (ms, value) => new Promise((resolve) => {setTimeout(() => resolve(value), ms);});
var promiseArray = [
waitThenTrigger(createPromise(3000, "a")),
waitThenTrigger(createPromise(2000, "b")),
waitThenTrigger(createPromise(1000, "c"))];
Promise.all(promiseArray).then(values => console.log(values));

Vue.js - search form with query parameters

I am using Vue.js 2.6 with the vue-router component. I have a search form as follows:
<form class="search-form" #submit.prevent="search">
<div class="form-group">
<input type="text" class="form-control" v-model="term" placeholder="Search">
</div>
</form>
And here is my script:
<script>
export default {
data() {
return {
term: this.$route.query.term,
items: []
}
},
created() {
if (this.term != null) {
this.search()
}
},
watch: {
'$route.query.term'() {
this.term = this.$route.query.term
this.search()
}
},
methods: {
search: function () {
window.axios.get('/images/search', {
params: {
term: this.term
}
})
.then(response => {
this.$router.push({query: { 'term' : this.term}})
this.items = response.data.collection.items
})
.catch(error => {
return error
})
}
}
}
</script>
What I am trying to achieve with this code is the following:
User submits form, the search() function is called. The URL is updated with the query param, e.g. /search?term=<term>. This is working but the search() function is being called twice.
User carries out several searches, then presses the back button. The search field in the form is updated and the search is carried out. This is working but the search() function is being called twice.
User manually enters query param in the URL bar. The search field in the form is populated and the search is carried out. This is working.
Where the search() function is being called twice, this is due to the watch() function, which is designed to watch changes to the URL bar. I am not sure how to combine this function correctly with the search() function.
In watch, you can compare new value with old value, and only perform search when new value is different with old value
watch: {
'$route.query.term'(newVal, oldVal) {
if (newVal != oldVal) {
this.term = this.$route.query.term
this.search()
}
}
},
To make it call only 1 for 1st case, you might want to separate button click handler with real search call
<script>
export default {
data() {
return {
term: this.$route.query.term,
items: []
}
},
created() {
if (this.term != null) {
this.performSearch()
}
},
watch: {
'$route.query.term': {
handler: function(newVal, oldVal) {
if (newVal != oldVal) {
this.term = this.$route.query.term
this.performSearch()
}
},
immediate: true
}
},
methods: {
search: function () {
// this is call when user click search Button
this.$router.push({query: { 'term' : this.term}})
},
performSearch() {
// perform actual searcch
window.axios.get('/images/search', {
params: {
term: this.term
}
})
.then(response => {
this.items = response.data.collection.items
})
.catch(error => {
return error
})
}
}
}
</script>

Update math equation using MathLive and Vue

I have been struggling to use Vue and MathLive to handle typesetting randomly generated numbers and their squares. The function of the program is to generate a random integer from 1 to 35, calculate the square, and typeset it with MathLive. There are two buttons that add one to the integer or create another random one. I have no problem typesetting the initial value but when I create a different integer or add 1 the page, it never re-typesets. I am trying to implement this program as a component in Vue. Here is my MWE (component only):
<template lang="html">
<div class="problem">
<p id="math">$${{num}}^2 = {{square()}}$$</p>
<button #click="addOne">Add One</button>
<button #click="randomInt">Random Number</button>
</div>
</template>
<script>
import math from 'mathjs'
import MathLive from 'mathlive'
export default {
name: 'Problem',
data: function () {
return {
num: math.randomInt(1,35)
}
},
watch: {
num: function () {
console.log("Data changed");
// this.renderMath();
}
},
created: function () {
console.log("Hello This is created!");
this.renderMath();
},
beforeMount: function () {
console.log("This is beforeMount");
},
mounted: function () {
console.log("This is mounted!");
},
beforeUpdate: function () {
console.log("This is beforeUpdate");
this.renderMath();
},
methods: {
addOne: function() {
this.num++
},
randomInt: function () {
this.num = math.randomInt(1,35)
},
square: function () {
return this.num**2
},
renderMath: function (event) {
this.$nextTick(function(){
MathLive.renderMathInElement("math");
})
}
}
}
</script>
<style lang="css" scoped>
#import url("../../node_modules/mathlive/dist/mathlive.core.css");
#import url("../../node_modules/mathlive/dist/mathlive.css");
p {
color: white;
}
</style>
Edit: To clarify when I load the page up, the initial value is typeset correctly using MathLive as shown below:
Then after I click either the Add One or Random Number button, the program should generate a new value, calculate its square, and update that value on the screen as shown below:
It seems MathLive's DOM manipulation conflicts with Vue's virtual DOM, preventing Vue from patching the DOM with the updated text node.
A workaround is to apply a key to force the MathLive p element to be re-created when the key changes. We could use num as the key, since it changes with each button press:
<p :key="num">...</p>
The current watcher on num would need to be updated to call renderMath() to refresh the MathLive element:
watch: {
num() {
this.renderMath();
}
},
You should also consider making square() a computed property for more efficient rendering:
// script
computed: {
square() {
return this.num ** 2
}
}
// template
<p :key="num">$${{num}}^2 = {{square}}$$</p>
You need to use vue.js computed properties
new Vue({
name: 'Problem',
data: function () {
return {
num: math.randomInt(1,35)
}
},
watch: {
num: function () {
console.log("Data changed");
this.renderMath();
}
},
computed: {
square: function () {
return this.num**2;
}
},
created: function () {
console.log("Hello This is created!");
this.renderMath();
},
beforeMount: function () {
console.log("This is beforeMount");
},
mounted: function () {
console.log("This is mounted!");
},
beforeUpdate: function () {
console.log("This is beforeUpdate");
//this.renderMath();
},
methods: {
addOne: function() {
this.num++
},
randomInt: function () {
this.num = math.randomInt(1,35)
},
renderMath: function (event) {
this.$nextTick(function(){
MathLive.renderMathInElement("math");
})
}
}
}).$mount("#app")
<script src="https://unpkg.com/mathjs/dist/math.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mathlive#0.26.0/dist/mathlive.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<span>$${{num}}^2 = {{square}}$$</span>
<span id="math"></span>
<button #click="addOne">Add One</button>
<button #click="randomInt">Random Number</button>
</div>

#onmouseup not firing at vuejs 2

Full code: https://github.com/kenpeter/test_vue_simple_audio_1
I attach #onmouseup to input range. When I drag the slider, progressChange seems not be called.
<input
type="range"
:min="0"
:step="1"
v-model="current"
:value="current"
:max="duration"
#onmouseup="progressChange()"
/>
Here is the methods
methods: {
timeChange: function () {
this.current = this.$refs.player.currentTime;
},
getDuration: function () {
this.duration = this.$refs.player.duration;
},
toggleStatus: function () {
var player = this.$refs.player;
this.isPause ? player.play() : player.pause();
this.isPause = !this.isPause;
},
next: function () {
if (this.audioIndex == this.songs.length - 1) {
if (this.repeat) {
this.audioIndex = 0;
}
} else {
this.audioIndex++;
}
},
prev: function () {
if (this.audioIndex == 0) {
if (this.repeat) {
this.audioIndex = this.songs.length - 1;
}
} else {
this.audioIndex--;
}
},
progressChange() {
console.log("progress change");
},
To answer this question for future reference for people who would be looking for similar issues:
The issue was the wrong name on calling event as VueJS uses syntax of #even where # replaces 'on`, so you have to use:
#mouseup
#click
#keyup:keyname
#input
#customEventEmitedByComponent

Categories

Resources