How to display an element in Vue component only after NProgress.done() - javascript

For displaying the loading status in a VueJS application I use the library NProgress. It works well and shows the loading bar and the spinning wheel. However the HTML content of the page is already rendered and displayed. I'd like to hide certain parts of the page while the request is running.
Is there any possibility to check programmatically for NProgress.done() and display the contents after it has been called?
I'd like something like this:
<template>
<div>
<NavBar />
<div v-show="check here for NProgress.done()">
<p>Here are all the nice things with placeholders for the data from the API.</p>
</div>
</div>
</template>
<script>
import NavBar from '#/components/NavBar';
export default {
components: {
NavBar
}
}
</script>
The part "check here for NProgress.done()" is what I don't know how to solve.

Looking through the documentation of NProgress, it looks like it exposes a ".status", which returns the value of the current loader, but returns a "null" when the loader isn't "started".
<template>
<div>
<div v-show="state.status == null">
<p>
Here are all the nice things with placeholders for the data from the
API.
</p>
</div>
</div>
</template>
<script>
import Vue from "vue";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
const state = Vue.observable(NProgress);
export default {
data: () => ({ state }),
mounted: function () {
NProgress.start(); // Start the bar loading
setTimeout(() => { // Perform your async workloads
NProgress.done(); // .done() to finish the loading
}, 5000);
},
};
</script>
You'd need to make NProgress reactive, so you can just use Vue.observable(state).

Related

Dynamic rendering of popup component vuejs

I would like to create a VueJS app and I have a problem. I would like to be able to display some components and when I will right click on it, a popup will be displayed. The problem is that the popup will be different for every component. I saw something with the component id but I don't know if It can be answer to my problem.
Example of code :
Vue.component('component', {
template: `<button v-on:click="showPopup()">Open popup</button>`
})
Vue.component('popup1', {
template: '<div>Some complex features ... </button>'
})
Vue.component('popup2', {
template: '<div>Another complex features ... </button>'
})
The idea here is that the component 'component' don't really know which popup to display. It will be the function showPopup that will know the popup.
You can send a simple data to know which popup should be show to your client,
for example:
in App.vue setup() section you have this :
const popup = reactive({
type: "none",
data: "hello world"
});
const popupToggle = ref(false);
function showPopup(type, data){
popup.type = type;
popup.data = data;
popupToggle.value = true;
}
function closePopup(){
popup.type = "none";
popup.data = "empty";
popupToggle.value = false;
}
and provide your functions into your project with :
provide("popupFunctions", {showPopup, closePopup});
and inject provided functions in other child documents with :
const {showPopup, closePopup} = inject("popupFunctions");
now all you need is call the functions which named showPopup and closePopup to change popup variable which you created before in your App.vue and check the popup type to show the targeted component as a popup to your client
Something like this in <template> section in your App.vue :
<popup-component v-if="popupToggle">
<popup-msgbox v-if="popup.type === 'msgBox'" :popup-data="popup.data" />
<popup-formbox v-else-if="popup.type === 'formBox'" :popup-data="popup.data" />
<popup-errorbox v-else-if="popup.type === 'errBox'" :popup-data="popup.data" />
</popup-component>
of course you should import these components and other required things in your project as you know, and i just tried to clear the solve way for you.
I hope my answer be clear to you and help you solve your problem.
Here's a working example: https://codesandbox.io/s/nervous-dew-kjb4ts
Step 1
Make a modal - see example.
We can use slots to put dynamic content, like other components in each instance of the modal, and named slots for multiple sections. We will control visibility in the outer component / the mixin.
<template>
<transition name="modal">
<div class="modal-header">
<slot name="header"> default header </slot>
</div>
<div class="modal-body">
<slot name="body"> default body </slot>
</div>
<slot name="footer">
Default Footer
<button class="modal-default-button" #click="$emit('close')">
🚫 Close
</button>
</slot>
</transition>
</template>
<script>
export default {
name: "Modal",
};
</script>
<style scoped>
// See link above for full styles
</style>
Step 2
Create a mixin that all components containing a modal can extend from. Here we'll put methods for opening, closing and anything else you need. Create a data attribute to indicate modal state for use with v-if, then add two methods for opening and closing.
import Modal from "#/components/Modal";
export default {
components: {
Modal
},
data: () => ({
modalState: false
}),
methods: {
openModal() {
this.modalState = true;
},
closeModal() {
this.modalState = false;
},
},
};
Step 3
Create your components, that extend from the mixin, and use the modal component with whatever content you like.
You can trigger right-clicks using: #mouseup.right
<template>
<div class="example-component comp1">
<h2>Component 1</h2>
<button #contextmenu.prevent
#mouseup.right="openModal()"
#click="tryRightClick()">
Open Component 1 Modal
</button>
<Modal v-if="modalState" #close="closeModal()">
<template v-slot:header>👉 Component 1 Modal</template>
<template v-slot:body>
Lorem ipsum
</template>
</Modal>
</div>
</template>
<script>
import modalMixin from "#/mixins/component-modal-mixin";
export default {
mixins: [modalMixin],
};
</script>
Step 4
Finally, just import your components.
<template>
<div id="app">
<h3>StackOverflow Answer for Terbah Dorian</h3>
<i>Example of separate components opening separate modals</i>
<Component1 />
<Component2 />
<Component3 />
</div>
</template>
<script>
import Component1 from "#/components/Component1";
import Component2 from "#/components/Component2";
import Component3 from "#/components/Component3";
export default {
name: "App",
components: {
Component1,
Component2,
Component3,
},
};
</script>
Hope that helps :)
If it did, then an upvote would be appreciated!
https://codesandbox.io/s/laughing-shape-18dnqq?file=/src/App.vue
I made a working sample to display dynamic popovers by hovering the buttons. You can use Slots in Vue components.
Hope this helps you.

Pass the clicked element's data by using eventbus in Vue.js

Im pretty new to Vue.js, thank you for your understanding. Im setting up a Vue project where I want to show Patients and their data. I want to tell from the beginning that Im not planning to use Vuex :)
My project has 3 layers.
Home.vue file where I import the data (patients)
Next layer is Patients.vue component where I have a for loop and output all the patients. In this case, I am getting the patient Array by using props.
And the last layer is called ViewPatient.vue view. What I want to do here is showing more details of the clicked Patient. I want to inherit for example the name to make one more call to the endpoint to retrieve some observations of the patient. For example: endpoint/patient/(theName) <-- the name should come from the previous Patients.vue component.
I tried a lot of different approaches: eventbus, dynamic router and data-attrbutes.
Home.vue
<template>
<div class="container">
<keep-alive>
<Patients :PatientsData="PatientsData" />
</keep-alive>
</div>
</template>
<script>
// # is an alias to /src
import PatientsData from "../data/messages";
import Patients from "../components/Patients.vue";
export default {
name: "home",
data() {
return {
PatientsData: PatientsData
};
},
components: {
Patients
}
};
</script>
Patients.vue (component)
<template>
<div v-if="PatientsData.length > 0">
<div class="row row-eq-height">
<div v-for="PatientData in PatientsData" class="col-12 col-sm-6 col-md-3 mb-3" :key="PatientData.content" :data-id="PatientData.content" #click.prevent="passPatientData" >
<router-link to="/patient" >
<div class="col-12 patientsTiles p-4">
<p class="patientsName">
<span>Navn</span>
{{ PatientData.content }}
</p>
<p class="patientsCPR">
<span>CPR.nr</span>
{{ PatientData.subject }}
</p>
<p class="patientsAge">
<span>Alder</span>
{{PatientData.age}}
</p>
<i :class="['fa', 'fa-star', {important: PatientData.isImportant}]"></i>
</div>
</router-link>
</div>
</div>
</div>
</template>
<script>
import router from "../main";
import { eventBus } from "../main";
export default {
props: {
PatientsData: Array,
},
data(){
return{
patientName: ""
}
},
methods: {
passPatientData() {
this.patientName = this.PatientData;
alert(this.patientName);
eventBus.$emit("passPatientData", this.patientName);
}
}
};
</script>
ViewPatient.vue (view)
<template>
<div class="container">
<h1>The Patient detail</h1>
</div>
</template>
<script>
// # is an alias to /src
import { eventBus } from "../main";
export default {
props: {
// patientId:{
// type: String
// }
},
data() {
return {
selectedPatient : ""
};
},
created() {
eventBus.$on("passPatientData", data => {
this.selectedPatient = data;
// console.log("yeaah");
})}
}
</script>
IMO, the problem is lying on the passPatientData function.
this.PatientData is empty and I dont know how to pass the clicked element's data to the empty string (this.patientName), so I can emit it to the eventbus
passPatientData() {
this.patientName = this.PatientData;
alert(this.patientName);
eventBus.$emit("passPatientData", this.patientName);
}
Here is my approach which worked for me (few changes):
#bbsimonbb, thank you for the answer, but I´m not going to use that approach, because its a bit overkill when compared to my small task.
In Patients.vue while looping patients, I have modified the click event:
Im actually passing the single element that is being clicked, which solved med a lot of time.
<div v-for="PatientData in storedPatients" class="col-12 col-sm-6 col-md-3 mb-3" :data-id="PatientData.content" #click="passPatientData(PatientData)" >
Before:
#click="passPatientData"
After:
#click="passPatientData(PatientData)"
And then in my event bus im able to "work" with the data im passing:
methods: {
passPatientData(element) {
this.patientName = element.content;
alert(this.patientName);
eventBus.$emit("passPatientData", this.patientName);
}
}
The purpose is to pass the patientName to ViewPatient.vue file by using eventbus and call a new endpoint which looks like this: endpoint/patient/(patientName) . The result of the endpoint will then be the details of the single patient that has been clicked in patients.vue
It´s working. Hope it can be useful for others that is struggling with the same issue.
You've decided not to use Vuex. That doesn't mean you shouldn't use a store. The simplest store is just an object in the data of your root Vue, passed around via provide/inject. Put your shared data in there.
This is a much simpler pattern to get your head around than an event bus. You get the shared state right out of Vue components, and because it's just an object, that does nothing but store state, you will be more in control of what you have and how it's working.
// Home.vue
export default {
name: "home",
provide: { mySharedData: this.mySharedData },
data() {
return {
mySharedData: {
patientData: {}
}
};
},
created() {
fetch("http://patientData")
.then(response.json)
.then((patientData) => this.mySharedData.patientData = patientData)
}
...
// Then, in all other components of your app...
export default {
inject: ['mysharedData'],
...
}
This way, you'll be making full use of Vue reactivity to propagate your changes. You'll need to understand how Vue makes properties reactive. In particular, you can't just assign new props in mySharedData. The simplest way is to make sure all the props are there to start with. To do it on the fly, use Vue.set().

Props passed into a a component do not render

I am passing some data values into a component but when I try to render that component, the data is not received correctly. I get an error stating:
Cannot read property 'title' of undefined at Proxy.render (eval at
./node_modules/cache-loader/dist/cjs.js?
I am sure the data I am attempting to receive is correct because after a second the data loads on the page correctly. However I need the data to load on page load because I plan on implementing an edit feature. I have done things similar to this throughout my project in different sections and it works perfectly. I am not sure why an error is being thrown here though.
I have attempted to change at what point the data is recognized, passing in concrete data values, etc. But nothing has worked.
Component:
<template>
<div class="column">
<h1 class="title">{{ this.relevantData.title }}</h1>
<div class="buttons are-large">
<a class="button is-info is-outlined" #click="journalPush()">Journal</a>
<a class="button is-success">Edit</a>
</div>
</div>
</template>
<script>
import router from "#/router.js";
export default {
props: ['relevantData'],
methods: {
journalPush() {
router.push("/recipes/" + this.relevantData.documentID + "/journal");
}
},
mounted() {
console.log(this.relevantData);
}
}
</script>
Parent:
<template>
<section class="overall">
<div class="is-mobile">
<div class="columns">
<Content :relevantData="sidebarData"/>
</div>
</div>
</section>
</template>
<script>
// # is an alias to /src
import Content from '#/components/recipes/contentComponent.vue';
import { db } from '#/main.js'
export default {
data() {
return {
sidebarData: null,
}
},
components: {
Sidebar,
},
created(){
db.collection('recipes').orderBy('dateMade', 'desc').get()
.then((snapshot) => {
this.sidebarData = snapshot.docs.map(doc => doc.data());
})
}
}
I expect when I log relevantData in the component in the mounted section, I should be able to see all the data immediately.
If you need the data to exist in the mounted hook then you'll need to use a v-if to delay the creation of the component:
<Content v-if="sidebarData" :relevantData="sidebarData"/>
Without the v-if the Content component will be created and rendered immediately, before the sidebarData has loaded from your server.

VUE Image with :src doesnt show up on first rendering

Hello i'm facing an issue on my avatar component, the image that i load from an url stocked in my vuex store doesn't show up on the first rendering, only at the second.
here's my avatar component
<template>
<img :src="getAvatarUrl()"/>
</template>
<script>
export default {
methods: {
getAvatarUrl() {
return this.$store.state.user.userAvatarUrl
}
}
}
</script>
here's how i commit the image url in my store from App.vue:
created() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
let avatarRef = firebase.storage().ref().child(`avatars/${this.$store.state.user.displayName}`)
avatarRef.getDownloadURL().then(url => {
this.$store.commit('userAvatarUrl', url)
})
}
})
}
This image from the avatar component doesn't render the first time it should,
I have to navigate on another route and come back to see it.
I tried to force rerender on all lifecycle hooks with :key and use this.$nexttick but that don't work too.
Thanks for the help
This is because the store doesn't contain the image path until the request has completed and the request will likely complete after the DOM and your component has rendered.
You just need to use a computed property instead:
<template>
<img :src="avatarUrl"/>
</template>
<script>
export default {
computed: {
avatarUrl() {
return this.$store.state.user.userAvatarUrl
}
}
}
</script>

Change a property's value in one component from within another component

I'm trying to wrap my head around hoe Vue.js works, reading lots of documents and tutorials and taking some pluralsight classes. I have a very basic website UI up and running. Here's the App.vue (which I'm using kinda as a master page).
(To make reading this easier and faster, look for this comment: This is the part you should pay attention to)...
<template>
<div id="app">
<div>
<div>
<CommandBar />
</div>
<div>
<Navigation />
</div>
</div>
<div id="lowerContent">
<!-- This is the part you should pay attention to -->
<template v-if="showLeftContent">
<div id="leftPane">
<div id="leftContent">
<router-view name="LeftSideBar"></router-view>
</div>
</div>
</template>
<!-- // This is the part you should pay attention to -->
<div id="mainPane">
<div id="mainContent">
<router-view name="MainContent"></router-view>
</div>
</div>
</div>
</div>
</template>
And then in the same App.vue file, here's the script portion
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import CommandBar from './components/CommandBar.vue';
import Navigation from './components/Navigation.vue';
#Component({
components: {
CommandBar,
Navigation,
}
})
export default class App extends Vue {
data() {
return {
showLeftContent: true // <--- This is the part you should pay attention to
}
}
}
</script>
Ok, so the idea is, one some pages I want to show a left sidebar, but on other pages I don't. That's why that div is wrapped in <template v-if="showLeftContent">.
Then with the named <router-view>'s I can control which components get loaded into them in the `router\index.ts\ file. The routes look like this:
{
path: '/home',
name: 'Home',
components: {
default: Home,
MainContent: Home, // load the Home compliment the main content
LeftSideBar: UserSearch // load the UserSearch component in the left side bar area
}
},
So far so good! But here's the kicker. Some pages won't have a left side bar, and on those pages, I want to change showLeftContent from true to false. That's the part I can't figure out.
Let's say we have a "Notes" component that looks like this.
<template>
<div class="notes">
Notes
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Notes extends Vue {
data() {
return {
showLeftContent: false // DOES NOT WORK
}
}
}
</script>
Obviously, I'm not handling showLeftContent properly here. It would seem as if the properties in data are scoped only to that component, which I understand. I'm just not finding anything on how I can set a data property in the App component and then change it in a child component when that child is loaded through a router-view.
Thanks!
EDIT:
I changed the script section of the Notes component from:
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Notes extends Vue {
data() {
return {
showLeftContent: false // DOES NOT WORK
}
}
}
</script>
to:
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Notes extends Vue {
mounted() {
this.$root.$data.showLeftContent = false;
}
}
</script>
And while that didn't cause any compile or runtime errors, it also didn't have the desired effect. On Notes, the left side bar still shows.
EDIT 2:
If I put an alert in the script section of the Notes component:
export default class Notes extends Vue {
mounted() {
alert(this.$root.$data.showLeftContent);
//this.$root.$data.showLeftContent = false;
}
}
The alert does not pop until I click on "Notes" in the navigation. But, the value is "undefined".
EDIT 3:
Struggling with the syntax here (keep in mind this is TypeScript, which I don't know very well!!)
Edit 4:
Inching along!
export default class App extends Vue {
data() {
return {
showLeftContent: true
}
}
leftContent(value: boolean) {
alert('clicked');
this.$root.$emit('left-content', value);
}
}
This does not result in any errors, but it also doesn't work. The event never gets fired. I'm going to try putting it in the Navigation component and see if that works.
As it says on #lukebearden answer you can use the emit event to pass true/false to the main App component on router-link click.
Assuming your Navigation component looks like below, you can do something like that:
#Navigation.vue
<template>
<div>
<router-link to="/home" #click.native="leftContent(true)">Home</router-link> -
<router-link to="/notes" #click.native="leftContent(false)">Notes</router-link>
</div>
</template>
<script>
export default {
methods: {
leftContent(value) {
this.$emit('left-content', value)
}
}
}
</script>
And in your main App you listen the emit on Navigation:
<template>
<div id="app">
<div>
<Navigation #left-content="leftContent" />
</div>
<div id="lowerContent">
<template v-if="showLeftContent">
//...
</template>
<div id="mainPane">
//...
</div>
</div>
</div>
</template>
<script>
//...
data() {
return {
showLeftContent: true
}
},
methods: {
leftContent(value) {
this.showLeftContent = value
}
}
};
</script>
A basic approach in a parent-child component relationship is to emit events from the child and then listen and handle that event in the parent component.
However, I'm not sure that approach works when working with the router-view. This person solved it by watching the $route attribute for changes. https://forum.vuejs.org/t/emitting-events-from-vue-router/10136/6
You might also want to look into creating a simple event bus using a vue instance, or using vuex.
If you'd like to access the data property (or props, options etc) of the root instance, you can use this.$root.$data. (Check Vue Guide: Handling Edge)
For your codes, you can change this.$root.$data.showLeftContent to true/false in the hook=mounted of other Components, then when Vue creates instances for those components, it will show/hide the left side panel relevantly.
Below is one demo:
Vue.config.productionTip = false
Vue.component('child', {
template: `<div :style="{'background-color':color}" style="padding: 10px">
Reach to root: <button #click="changeRootData()">Click me!</button>
<hr>
<slot></slot>
</div>`,
props: ['color'],
methods: {
changeRootData() {
this.$root.$data.testValue += ' :) '
}
}
})
new Vue({
el: '#app',
data() {
return {
testValue: 'Puss In Boots'
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<h2>{{testValue}}</h2>
<child color="red"><child color="gray"><child color="green"></child></child></child>
</div>

Categories

Resources