How to wait for request and user input? - javascript

Let's say when a component loads I make an async request. That component also has a submit button that the user can press which triggers a function that relies on the result of that original request. How do I delay executing the triggered function until the async request is finished?
If that doesn't make sense let me give an example. MyComponent makes an async request getRandomColor() on mounted. MyComponent's template has <button #click="handleClick">. handleClick calls some function saveColor(). How do I make sure that saveColor() is not called until my async getRandomColor() is finished?
I'm currently using Vue.js but I think this question applies to all of javascript.

You can achieve this by adding :disabled attribute in your button element. The value of :disabled will be based on the response. i.e. If response will be there then enabled it otherwise disabled.
Working Demo :
const app = Vue.createApp({
el: '#app',
data() {
return {
buttonText: 'Call Me!',
apiResponse: [],
isDisabled: false
}
},
methods: {
saveColor() {
console.log('saveColor method call');
}
},
mounted() {
axios.get("https://jsonplaceholder.typicode.com/users").then(response => {
this.apiResponse = response.data; // Here we are getting proper response. hence, button is getting enabled.
}).catch((error) => {
console.warn('API error');
});
}
})
app.mount('#app')
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="app">
<button v-on:click="saveColor()" :disabled="!apiResponse.length">{{ buttonText }}</button>
</div>
Adding below snippet as per the comment added by the author of the post.
What if I didn't want to use the disabled button? Is there a way to make the button handler wait for the request to finish before it continues execution?
const app = Vue.createApp({
el: '#app',
data() {
return {
buttonText: 'Call Me!',
apiResponse: [],
isDisabled: false
}
},
methods: {
saveColor() {
console.log('saveColor method call');
}
},
mounted() {
axios.get("https://jsonplaceholder.typicode.com/users").then(response => {
this.apiResponse = response.data; // Here we are getting proper response. hence, button is getting enabled.
}).catch((error) => {
console.warn('API error');
});
}
})
app.mount('#app')
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="app">
<button v-on:click.prevent="apiResponse.length ? saveColor() : {}">{{ buttonText }}</button>
</div>

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

v-if="loading" works for md-progress-bar but doesn't work for anything else

Below is the code I am using:
<template>
<md-table>
<md-table-toolbar>
<div v-if="loading">
<h1> Hi </h1>
</div>
<md-progress-bar md-mode="query" v-if="loading"></md-progress-bar>
</md-table-toolbar>
</md-table>
</template>
<script>
export default {
name: "ordered-table",
props: {
tableHeaderColor: {
type: String,
default: ""
}
},
data() {
return {
selected: [],
files: getData(),
loading: false
};
},
methods: {
getData: async function (event) {
this.loading = true;
this.files = await getData();
this.loading = false;
}
}
};
function getData() {
var url = "http://localhost:4999/";
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", url, false);
xmlHttp.send(null);
return JSON.parse(xmlHttp.responseText);
}
</script>
v-if="loading" on the md-progress-bar shows/hides perfectly fine. But adding v-if="loading" to ANYTHING ELSE just causes the element to disappear forever. I've tried adding v-if="loading" to many other different types of elements and it doesn't work for any of them! I tried deleting the md-progress-bar and v-if still doesn't work. I don't understand what the hell I am doing wrong. I feel like the guides explaining v-if have been really straightforward yet for some reason it only works for md-progress-bar......
Do you want the disappearing elements to show when loading is finished? If so:
<div v-if="loading">
<h1> Hi </h1>
</div>
This element will only be shown when loading === false. For an element to be shown when data is done loading, use v-if="!loading"

Vue watcher executed before the new data is bound?

I am using this code:
var vueApp = new Vue({
el: '#app',
data: {
modalKanji: {}
},
methods: {
showModalKanji(character) {
sendAjax('GET', '/api/Dictionary/GetKanji?character=' + character, function (res) { vueApp.modalKanji = JSON.parse(res); });
}
},
watch: {
'modalKanji': function (newData) {
setTimeout(function () {
uglipop({
class: 'modalKanji', //styling class for Modal
source: 'div',
content: 'divModalKanji'
});
}, 1000);
}
}
});
and I have an element that when clicked on, displays a popup with the kanji data inside:
<span #click="showModalKanji(kebChar)" style="cursor:pointer;>
{{kebChar}}
</span>
<div id="divModalKanji" style='display:none;'>
<div v-if="typeof(modalKanji.Result) !== 'undefined'">
{{ modalKanji.Result.literal }}
</div>
</div>
It works, but only when used with a setTimeout delay to "let the time for Vue to update its model"...if I remove the setTimeout so the code is called instantaneousely in the watch function, the popup data is always "1 iteration behind", it's showing the info of the previous kanji I clicked...
Is there a way for a watcher function to be called AFTER Vue has completed is binding with the new data?
I think you need nextTick, see Async-Update-Queue
watch: {
'modalKanji': function (newData) {
this.$nextTick(function () {
uglipop({
class: 'modalKanji', //styling class for Modal
source: 'div',
content: 'divModalKanji'
});
});
}
}

Vue.js component model update

Im absolutely new in Vue framework and I need create reusable component with live BTC/LTC/XRP price
For live prices Im using Bitstamp websockets API. Here is example usage with jQuery - run this snippet, is really live.
var bitstamp = new Pusher('de504dc5763aeef9ff52')
var channel = bitstamp.subscribe('live_trades')
channel.bind('trade', function (lastTrade) {
$('p').text(lastTrade.price)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<h3>BTC/USD price</h3>
<p>loading...</p>
As you can see, its really simple. But, I need to use Vue.js component. So I created this, and its also fully functional:
var bitstamp = new Pusher('de504dc5763aeef9ff52')
Vue.component('live-price', {
template: '<div>{{price}}</div>',
data: function () {
return {
price: 'loading...'
}
},
created: function () {
this.update(this)
},
methods: {
update: function (current) {
var pair = current.$attrs.pair === 'btcusd'
? 'live_trades'
: 'live_trades_' + current.$attrs.pair
var channel = bitstamp.subscribe(pair)
channel.bind('trade', function (lastTrade) {
current.price = lastTrade.price
})
}
}
})
new Vue({
el: '.prices'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<section class="prices">
<live-price pair="btcusd"></live-price>
<live-price pair="ltcusd"></live-price>
<live-price pair="xrpusd"></live-price>
</section>
But, there is big BUT. Am I using Vue right way? WHERE IS IDEAL PLACE to run Pusher? In "created" or "mounted" method? In "computed"? In "watch"? Or where? Am i doing it right? I really dont known, I started with Vue ... today :(
Looks pretty good for your first day using Vue! I would just make a few changes.
The component is reaching out and using a global, bitstamp. Generally with components, you want them to be independent, and not reaching out of themselves to get values. To that end, declare the socket as a property that can be passed in to the component.
Likewise, the pair is passed in as a property, but you do not declare it and instead, use current.$attrs.pair to get the pair. But that's not very declarative and makes it harder for anyone else to use the component. Moreover, by making it a property, you can reference it using this.pair.
When using something like a socket, you should always remember to clean up when you are done using it. In the code below, I added the unsubscribe method to do so. beforeDestroy is a typical lifecycle hook to handle these kinds of things.
Computed properties are useful for calculating values that are derived from your components data: the channel you are subscribing to is a computed property. You don't really need to do this, but its generally good practice.
A Vue can only bind to a single DOM element. You are using a class .prices which works in this case because there is only one element with that class, but could be misleading down the road.
Finally, created is an excellent place to initiate your subscription.
console.clear()
var bitstamp = new Pusher('de504dc5763aeef9ff52')
Vue.component('live-price', {
props:["pair", "socket"],
template: '<div>{{price}}</div>',
data() {
return {
price: 'loading...',
subscription: null
}
},
created() {
this.subscribe()
},
beforeDestroy(){
this.unsubscribe()
},
computed:{
channel(){
if (this.pair === 'btcusd')
return 'live_trades'
else
return 'live_trades_' + this.pair
}
},
methods: {
onTrade(lastTrade){
this.price = lastTrade.price
},
subscribe() {
this.subscription = this.socket.subscribe(this.channel)
this.subscription.bind('trade', this.onTrade)
},
unsubscribe(){
this.subscription.unbind('trade', this.onTrade)
this.socket.unsubscribe(this.channel)
}
}
})
new Vue({
el: '#prices',
data:{
socket: bitstamp
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<section id="prices">
<live-price pair="btcusd" :socket="bitstamp"></live-price>
<live-price pair="ltcusd" :socket="bitstamp"></live-price>
<live-price pair="xrpusd" :socket="bitstamp"></live-price>
</section>
Rewrited - is it ok now?
var config = {
key: 'de504dc5763aeef9ff52'
}
var store = new Vuex.Store({
state: {
pusher: null
},
mutations: {
initPusher (state, payload) {
state.pusher = new Pusher(payload.key)
}
}
})
var livePrice = {
template: '#live-price',
props: ['pair'],
data () {
return {
price: 'loading...',
subscription: null
}
},
computed: {
channel () {
return this.pair === 'btcusd'
? 'live_trades'
: 'live_trades_' + this.pair
}
},
methods: {
onTrade (lastTrade) {
this.price = lastTrade.price
},
subscribe () {
this.subscription = this.$store.state.pusher.subscribe(this.channel)
this.subscription.bind('trade', this.onTrade)
},
unsubscribe () {
this.subscription.unbind('trade', this.onTrade)
this.$store.state.pusher.unsubscribe(this.channel)
}
},
created () {
this.subscribe()
},
beforeDestroy () {
this.unsubscribe()
}
}
new Vue({
el: '#prices',
store,
components: {
'live-price': livePrice
},
created () {
store.commit({
type: 'initPusher',
key: config.key
})
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.3.1/vuex.min.js"></script>
<section id="prices">
<live-price pair="btcusd"></live-price>
<live-price pair="ltcusd"></live-price>
<live-price pair="xrpusd"></live-price>
</section>
<template id="live-price">
<div>
{{price}}
</div>
</template>

call a component from another component in vue.js

I have an alert component like in this video: https://laracasts.com/series/learning-vue-step-by-step/episodes/21 And I have another component (Book). When a book was created how can I call Alert component in the success callback function like this:
<alert>A book was created successfully !!!</alert>
I am a newbie in using vue.js. Thank you for your help.
Updated: This is my code
submit: function () {
this.$http.post('/api/books/add', {
data: this.data,
}).then(function (response) {
// I want to use Alert component right here to notice to users.
}, function (response) {
});
}
Update 2:
Alert Component
<template>
<div class="Alert Alert--{{ type | capitalize }}"
v-show="show"
transition="fade"
>
<slot></slot>
<span class="Alert__close"
v-show="important"
#click="show = false"
>
x
</span>
</div>
</template>
<script>
export default {
props: {
type: { default: 'info' },
timeout: { default: 3000 },
important: {
type: Boolean,
default: false
}
},
data() {
return {show: true};
},
ready() {
if (!this.important)
{
setTimeout(
() => this.show = false,
this.timeout
)
}
}
}
</script>
<style>
.Alert {
padding: 10px;
position: relative;
}
.Alert__close {
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
}
.Alert--Info {
background: #e3e3e3;
}
.fade-transition {
transition: opacity 1s ease;
}
.fade-leave {
opacity: 0;
}
</style>
And in Book.vue I want to do like this:
// resources/assets/js/components/Book.vue
<template>
.....
<alert>A book was created successfully !!!</alert>
//Create book form
....
</template>
<script>
export default {
methods: {
submit: function () {
this.$http.post('/api/books/add', {
data: this.data,
}).then(function (response) {
this.$refs.alert
}, function (response) {
});
}
</script>
this JSfiddle does what you're looking for: https://jsfiddle.net/mikeemisme/s0f5xjxu/
I used a button press rather than a server response to trigger the alert, and changed a few method names, but principle is the same.
The alert component is nested inside the button component. Button passes a showalert prop to the alert component with the sync modifier set.
<alert :showalert.sync="showalert" type="default" :important="true">A book was saved successfully</alert>
Press the button, showalert is set to 'true', 'true' passed to alert as prop, alert displays as v-show condition is now true,
data() {
//by default we're not showing alert.
//will pass to alert as a prop when button pressed
//or when response from server in your case
return {
showalert: false
};
},
a watch on the showalert prop in alert component sees a change and triggers a method that sets showalert back to 'false' after whatever many seconds set in timeout property.
//this method is triggered by 'watch', below
//when 'showalert' value changes it sets the timeout
methods: {
triggerTimeout: function() {
//don't run when detect change to false
if (this.showalert === true) {
setTimeout(
() => this.showalert = false,
this.timeout
)
}
},
},
watch: {
// detect showalert being set to true and run method
'showalert': 'triggerTimeout',
}
Because this prop is synched back to parent, button state updated too.
It works but using watch etc. feels overblown. Vue may have a better way to handle this. I'm new to Vue so somebody with more knowledge might chime in.
Add a data property
alertShow: false
Next, in the callback:
this.alertshow = true;
When you want to remove it, set it to false.
In the component add a directive:
v-show="alertshow"
Update:
Add a components attribute to block component.
components: {Alert},
Next outside of the component, import the Alert component file:
import Alert from './directory/Alert.vue'
The above is if you are using vueify. Otherwise, add a component using
Vue.component
Check out the docs.
Update 2:
Your code, with the changes:
<script>
import Alert from './directory/alert.vue';
export default {
components: {
Alert
},
methods: {
submit: function () {
this.$http.post('/api/books/add', {
data: this.data,
}).then(function (response) {
this.$refs.alert
}, function (response) {
});
}

Categories

Resources