Changing a button to say "Loading" with a loading animation on click - javascript

I have created a Vue button that displays "Load More" and then "Loading..." when clicked and loading more content. But, I would now like to add another component being a loading animation next to the "Loading." The button works completely fine, but I just would like to add that animation alongside the word "loading."
I have tried using Vue's ref tag, but have not had much luck in successfully using that in my method.
Loader.vue:
<template>
<div
ref="sdcVueLoader"
class="sdc-vue-Loader"
>
Loading...
</div>
</template>
<script>
export default {
name: 'Loader'
</script>
App.vue:
<Button
:disabled="clicked"
#click="loadMore"
>
{{ loadMoreText }}
</Button>
<script>
import Button from './components/Button'
import Loader from './components/Loader'
export default {
name: 'ParentApp',
components: {
Button,
Loader
},
data () {
return {
loadMoreText: 'Load More',
clicked: false
}
},
methods: {
loadMore () {
if ... {
this.page += 1
this.loadMoreText = 'Loading...' + this.$refs.sdcVueLoader
this.clicked = true
this.somerequest().then(resp => {
this.clicked = false
this.loadMoreText = 'Load More'
})
return this.loadMoreText
}
}
</script>
I am hoping for the button to continue working as it is now, but now to also have the "Loader" component displaying next to "Loading..." when the button is clicked in the app.vue loadMore method.

If you want to do anything with any form of complexity in html, it is best to move it over to your template. In your case, you have two states: It is either loading, or it is not loading. So lets create a variable loading that is either true or false.
data () {
return {
loading: false,
page: 1,
}
},
methods: {
async loadMore () {
if (this.loading) {
return;
}
this.page += 1;
this.loading = true;
const response = await this.somerequest();
this.loading = false;
// Oddly enough, we do nothing with the response
}
}
Now, in the template use a v-if with a v-else:
<button
:disabled="loading"
#click="loadMore"
>
<template v-if="loading">
<icon name="loader" />
Loading...
</template>
<template v-else>
Load more
</template>
</button>
If you want to move the logic to a different component, you have two options:
Add loading as a prop to that different component, and move the template code to that component
Use a slot and pass the html directly into your loading button. This is especially useful if you have several different configurations, and don't want to deal with increasingly complex configuration options just to accommodate them all.

Related

PointerEvent object being returned instead of child data on emit

I am working on creating a vue component library. I have build a button component that has data of it's width and left position. I'm trying to emit that data to the parent (a tabs component) when it's clicked. I have troubleshooted quite a bit, and have narrowed down most of the problem. My child component (button) is emitting the correct thing, but it looks like the parent component (tabs) is receiving the value of the click/pointerevent object instead of the data passed on the emit. I'm certain this is some issue in my parent click handle method, but can't pinpoint what exactly. I've included code snippets for the components and their click handler methods.
This is pared down, but essentially, I want to emit the width (and eventually left position) of the child button to the parent tab upon clicking the child/button. I want to assign that emitted width/left position to the slider to move some reactive underlining whenever a button is clicked in the tabs. I built in a console log statement on the click event that returns the emitted value from the child, and then returns the received value from the parent. Right now, the child is emitting the correct value when button is clicked, but parent is receiving and trying to assign a PointerEvent object. Thanks for any feedback!
Child (button) template and relevant script:
<template>
<div class="button #click="click" ref="button">
<slot />
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'Button',
emits: [
'click'
],
data () {
return {
width: '',
left: ''
}
},
setup() {
const button = ref(null)
return {
button
}
},
mounted () {
this.$nextTick(() => {
this.left = Math.ceil(this.button.getBoundingClientRect().left)
this.width = Math.ceil(this.button.getBoundingClientRect().width)
})
},
methods: {
click () {
this.$emit('click', this.width)
console.log(`${this.width} has been emitted to the tabs component`)
}
}
}
</script>
Parent (tab) template and relevant script:
<template>
<div class="tabs" #click="updateSliderWidth">
slot
</div>
<div class="slider" :style="sliderWidth">
</template>
<script>
import Button from './Button.vue'
export default {
name: 'Tabs',
components: {
Button
},
methods: {
updateSliderWidth (value) {
this.sliderWidth = value
console.log(`${value} has been received and assigned by parent`)
}
},
data () {
return {
sliderWidth: ''
}
}
}
</script>
I can't see any problems with your code, except that you don't use the Button component in the parent component. Instead you are using a div. This would explain, why you're getting a PointerEvent. This Event is passed as first parameter to the event, if you don't pass anything explicitly.
Here a demo: https://stackblitz.com/edit/vue-opruyd?file=src%2FApp.vue

Lazy load Vue components once, retain data

i'm lazy loading some components, but these components do AJAX requests. I want the component's to retain their data if they are hidden again, as to not do multiple AJAX requests.
This is my main component which contains both lazy loaded components:
<template>
<div class="media-component">
<vehicle-image-slider :vehicle-id="vehicleId"
v-if="active === 'vehicle-image-slider'"
key="vehicle-image-slider"></vehicle-image-slider>
<vehicle-360-viewer :vehicle-id="vehicleId"
v-if="active === 'vehicle-360-viewer'"
key="vehicle-360-viewer"></vehicle-360-viewer>
Slider
360
</div>
</template>
<script>
const Vehicle360viewer = () => import('./Vehicle360Viewer.vue');
const VehicleImageSlider = () => import('./VehicleImageSlider.vue');
export default {
data: function() {
return {
active: 'vehicle-image-slider'
}
},
components: {
'vehicle-360-viewer': Vehicle360viewer,
'vehicle-image-slider': VehicleImageSlider
},
props: [ 'vehicleId' ]
}
</script>
I'd imagine v-if is the wrong thing to use on the components, however I don't want to load the component unless it is displayed.

Nuxt: destroy and recreate current page's component without refresh

I have a bunch of Vue components and I'm using Nuxt as the routing layer. My global layout is pretty standard:
<template>
<nav>
<nuxt-link to="/foo">foo</nuxt-link> | <nuxt-link to="/bar">bar</nuxt-link> | etc.
</nav>
<main>
<nuxt />
</main>
</template>
In each page, I update the query string when data in the vuex store changes. If the page is loaded server-side, I parse the query string to pre-load the necessary data into the store:
<template>
<h1>foo</h1>
<!-- forms and stuff -->
</template>
<script>
export default {
data() {
return {
// static data
}
},
computed: {
fooStoreParams() {
return this.$store.state.foo.params
}
},
watch: {
fooStoreParams: async function() {
await this.$nextTick()
let query = {}
if (this.fooStoreParams.page > 1) {
query.page = this.fooStoreParams.page
}
this.$router.push({ path: this.$route.path, query: query })
}
},
async asyncData({ store, query }) {
let params = {}
let data = {
form: {
page: 1
}
}
if (query.page) {
data.form.page = query.page
params.page = query.page
}
store.dispatch('foo/updateParams', params)
await store.dispatch('foo/getStuffFromAPI')
return data
}
}
</script>
This works well, but there's a feature that I'm missing.
If I'm on already on /foo?page=2&a=1&b=2 and I click on the /foo link in the main navigation, nothing happens. This makes sense considering how Nuxt/vue-router works, but what I want to happen is for the page component to be reloaded from scratch (as if you had navigated from /bar to /foo).
The only ways I can think to do this are to either 1) do a server-side request (e.g. <b-link href="/foo">) if I'm already on /foo?whatever or 2) write a resetPage() method for each individual page.
Is there a way to just tell Nuxt to destroy and recreate the current page component?
You need to use watchQuery in order to enable client-navigation for query-params:
watchQuery: ['page', 'a', 'b']
https://nuxtjs.org/api/pages-watchquery/
If you have a component e.g
<titlebar :key="somekey" />
and the value of somekey changes the component re-renders. You could maybe work around this to achieve what you want. Read more here: https://michaelnthiessen.com/force-re-render/

Dynamic / Async Component Render

I am quite new to VueJS and have been playing around with the framework for a couple of days.
I am building a sort of dashboard with a widget based look and feel and the problem I have is that when the user adds a lot of widgets to the dashboard, problems arise on the loading of the page since the widgets make simultaneous calls to the API's to retrieve subsets of data.
To give you a better understanding of what I am doing, the concept is the below. (This is a brief idea to keep the code clean and simple).
Home.vue
<template>
<div class="Home">
<h1>Homepage</h1>
<div v-for="w in widgets">
<component :is="widget"></component>
</div>
</div>
</template>
<script>
export default {
name: 'Home',
mounted() {
for (var i = 0; i < availableWidgets; i++) {
widgets.push(availableWidgets);
}
},
};
</script>
Widget 1
<template>
<div class="Widget1">
<span>Widget 1</span>
</div>
</template>
<script>
export default {
name: 'Widget1',
mounted() {
//Get data from API and render
},
};
</script>
Widget 2
<template>
<div class="Widget2">
<span>Widget 2</span>
</div>
</template>
<script>
export default {
name: 'Widget2',
mounted() {
//Get data from API and render
},
};
</script>
As you can see, I am sort of loading the widgets and adding them dynamically depending on what the user has in his dashboard.
The problem I have is that Widget 1 and Widget 2 (in my case there are like 20-30 widgets), will be making API calls and this works fine when 1 or 2 widgets are loaded. But once the page grows a lot and there will be like 10 widgets on the page, everything starts lagging.
What would you suggest to do to make this more performant? Is it possible to allow once component to load at a time before loading the second component and so on? I was thinking of adding async calls, but that would not stop the components from being loaded at the same time?
Looking forward to your feedback and help that you could provide.
A common pattern would be to have the first render be without data, then re-render whenever your data comes in. The browser will make sure that not too many network requests run at the same time, so you should not have lag perse from that. You just perceive lag, because your component does not render until the data loads.
I would suggest using something like Axios, which uses promises and makes it easy to create asynchronous http requests while still keeping your code readable.
<template>
<div class="widget graph">
<div v-if="loading">
<span>Loading...</span>
<img src="assets/loader.svg">
</div>
<div v-else>
<!-- Do whatever you need to do whenever data loads in -->
</div>
</div>
</template>
<script>
export default {
name: 'WidgetGraph',
data () {
return {
loading: true,
error: null,
graphData: {}
}
},
created () {
this.loadData();
},
methods: {
loadData () {
return axios.get(...).then((data) => {
this.loading = false;
}).catch(() => {
this.error = 'Something went wrong.... Panic!';
this.loading = false;
});
}
}
}
</script>
<style>
</style>

Loading page in ReactJS and constant reloading component

In my project I have some components, one of them is a div which consists several other components and is a button and its use is like a menu.
Trying to make a loading page overlayed to prevent actions during the charge of components, I modified my code:
1) Including in the constructor:
this.state = {
isLoaded: false
}
2) Adding a componentDidMount() method:
componentDidMount() {
this.setState({
isLoaded: true
})
}
3) Changing the render method:
const isLoaded = this.state.isLoaded;
if (!isLoaded) {
document.getElementById("loadingPageHTML").style.display = "inline-block";
return(
<p className = "noShowIt"> Hola </p>
);
} else {
document.getElementById("loadingPageHTML").style.display = "none";
return (
<div className="MainMenuButtons">
...
}
But when I debug on my browser I see that when I press one of these buttons, the flow is always enter in the render method of this component, so, the loading page display is always inline-block status even if the second component (with the same state and conditions) isn't loaded.
What's the way to develop a loading lay with a good behaviour?
Is it possible to do it in a similar way as ajaxStart function in jQuery?
You're kind of mixing react components with an older style of web development.
You'd want to render a loading component or your content components based on the isLoaded state rather then using css and dom selectors. for example:
render () => (!isLoaded) ? <LoadingComponent /> : <Content />;
render() {
if (!isLoaded) {
return <LoadingComponent />;
} else {
return (<div> ... </div>);
}
}
When the state changes the components being rendered will automatically update

Categories

Resources