Vue Mounting Component removing div - vue.js

I am trying to mount a component on a function, it works fine. However I've got it setup so that it destroys the div after X amount of seconds. Then when I try and add the compoent again its removed the base div. I'm not sure how to fix this though...
Component:
<template>
<div>
<b-alert show dismissible variant="danger">
<i class="mdi mdi-block-helper mr-2"></i>{{ text }}
</b-alert>
</div>
</template>
<script>
export default {
name: "alertDanager",
props: {
text: null
},
created() {
setTimeout(() => this.destoryEl(), 5000);
},
methods: {
destoryEl() {
this.$destroy();
this.$el.parentNode.removeChild(this.$el);
}
}
};
</script>
Spawning the component in
const DangerAlertExtended = Vue.extend(dangerAlert);
const error = new DangerAlertExtended({ propsData: { text: "Error message" } });
error.$mount("#error");
I'm not sure how to make it stop overwriting the #error div...

Why not let v-if exclude it from the DOM instead of removing the element? Alternatively, it could be hidden but remain in the DOM with v-show (that's probably what I would do, unless there's a specific reason you need for it to not be in the DOM). I think it's generally better to let Vue manage the DOM rather than manipulate yourself.
<template>
<div v-if="showAlert">
<b-alert show dismissible variant="danger">
<i class="mdi mdi-block-helper mr-2"></i>{{ text }}
</b-alert>
</div>
</template>
<script>
export default {
name: "alertDanager",
props: {
text: null,
showAlert: true
},
created() {
setTimeout(() => this.hideAlert(), 5000);
},
methods: {
hideAlert() {
this.showAlert = false
}
}
}
</script>

Related

How do have unique variables for each dynamically created buttons/text fields?

I'm trying to create buttons and vue element inputs for each item on the page. I'm iterating through the items and rendering them with v-for and so I decided to expand on that and do it for both the rest as well. The problem i'm having is that I need to to bind textInput as well as displayTextbox to each one and i'm not sure how to achieve that.
currently all the input text in the el-inputs are bound to the same variable, and clicking to display the inputs will display them all at once.
<template>
<div class="container">
<div v-for="(item, index) in items" :key="index">
<icon #click="showTextbox"/>
<el-input v-if="displayTextbox" v-model="textInput" />
<el-button v-if="displayTextbox" type="primary" #click="confirm" />
<ItemDisplay :data-id="item.id" />
</div>
</div>
</template>
<script>
import ItemDisplay from '#/components/ItemDisplay';
export default {
name: 'ItemList',
components: {
ItemDisplay,
},
props: {
items: {
type: Array,
required: true,
},
}
data() {
displayTextbox = false,
textInput = '',
},
methods: {
confirm() {
// todo send request here
this.displayTextbox = false;
},
showTextbox() {
this.displayTextbox = true;
}
}
}
</script>
EDIT: with the help of #kissu here's the updated and working version
<template>
<div class="container">
<div v-for="(item, index) in itemDataList" :key="itemDataList.id">
<icon #click="showTextbox(item.id)"/>
<El-Input v-if="item.displayTextbox" v-model="item.textInput" />
<El-Button v-if="item.displayTextbox" type="primary" #click="confirm(item.id)" />
<ItemDisplay :data-id="item.item.uuid" />
</div>
</div>
</template>
<script>
import ItemDisplay from '#/components/ItemDisplay';
export default {
name: 'ItemList',
components: {
ItemDisplay,
},
props: {
items: {
type: Array,
required: true,
},
}
data() {
itemDataList = [],
},
methods: {
confirm(id) {
const selected = this.itemDataList.find(
(item) => item.id === id,
)
selected.displayTextbox = false;
console.log(selected.textInput);
// todo send request here
},
showTextbox(id) {
this.itemDataList.find(
(item) => item.id === id,
).displayTextbox = true;
},
populateItemData() {
this.items.forEach((item, index) => {
this.itemDataList.push({
id: item.uuid + index,
displayTextbox: false,
textInput: '',
item: item,
});
});
}
},
created() {
// items prop is obtained from parent component vuex
// generate itemDataList before DOM is rendered so we can render it correctly
this.populateItemData();
},
}
</script>
[assuming you're using Vue2]
If you want to interact with multiple displayTextbox + textInput state, you will need to have an array of objects with a specific key tied to each one of them like in this example.
As of right now, you do have only 1 state for them all, meaning that as you can see: you can toggle it for all or none only.
You'll need to refactor it with an object as in my above example to allow a case-per-case iteration on each state individually.
PS: :key="index" is not a valid solution, you should never use the index of a v-for as explained here.
PS2: please follow the conventions in terms of component naming in your template.
Also, I'm not sure how deep you were planning to go with your components since we don't know the internals of <ItemDisplay :data-id="item.id" />.
But if you also want to manage the labels for each of your inputs, you can do that with nanoid, that way you will be able to have unique UUIDs for each one of your inputs, quite useful.
Use an array to store the values, like this:
<template>
<div v-for="(item, index) in items" :key="index">
<el-input v-model="textInputs[index]" />
</div>
<template>
<script>
export default {
props: {
items: {
type: Array,
required: true,
},
},
data() {
textInputs: []
}
}
</script>

Vue3 objects from Array only rendering after making a small change in component

ers,
Experiencing a strange rendering issue. I am grabbing user data from localForage located in my Vuex store in a promise in the following component:
<template>
<div>
<h1>Users available for test {{ $route.params.id }}</h1>
<v-form>
<div v-if="this.import_complete">
<UserList
:users="users"
/>
</div>
</v-form>
</div>
</template>
<script>
import UserList from './UserList.vue';
export default {
name: 'UserManagement',
components: {
UserList,
},
data: () => ({
users: [],
import_complete: false,
}),
mounted() {
Promise.resolve(this.$store.getters.getUsersByTestId(
this.$route.params.testId,
)).then((value) => {
this.users = value;
this.import_complete = true;
});
},
};
</script>
Since it's a promise, I am setting a boolean import_complete to true, and a div in the template is only passing through the data as a prop when this boolean is true
Next, I am consuming the data in another template, in a for loop.
<template>
<div>
<v-container>
<v-banner v-for="user in this.users" :key="user.index">
{{ user.index }} {{ user.name }} {{ user.profile }}
<template v-slot:actions>
<router-link
:to="`/usering/${user.test}/user/${user.index}`">
<v-btn text color="primary">Open usering analysis</v-btn>
</router-link>
<v-btn text color="warning" #click="deleteUser(user.index)">Delete</v-btn>
</template>
</v-banner>
</v-container>
</div>
</template>
<script>
export default {
name: 'UserList',
props: {
users: Object,
},
methods: {
deleteUser(index) {
this.$store.dispatch('delete_user', index);
},
},
mounted() {
console.log('mounted user list, here come the users');
console.log(this.users);
},
};
</script>
The thing is, the first time it doesn't show anything. Only when I make a tiny change in the last component (can be an Enter followed by a save command) and suddenly the users are displayed on the page.
Interestingly, in the first scenario, the user's array is already filled, I see it in the console (created in the mount method) as well in the Chrome developer Vue tab.
It's probably some kind of Vue thing I am missing? Does someone have a clue?
[edit]
I've changed the code to this, so directly invoking the localForage. It seems to work, but I would still like to understand why the other code won't work.
this.test = this.$store.getters.getTestByTestId(this.$route.params.testId);
this.test.store.iterate((value, key) => {
if (key === (`user${this.$route.params.userId}`)) {
this.user = value;
}
}).then(() => {
this.dataReady = true;
}).catch((err) => {
// This code runs if there were any errors
console.log(err);
});

Vue: How to change a value of state and use it in other page and change page structure at starting by it?

In this project, we can login from login.vue by clicking login button and if it is success then we can see Lnb.vue in dashboard.vue
I thought if i code like this then pageSso will be 1 when I check the checkbox in login.vue in Lnb.vue then it will not show only "Account" menu.
When I used console.log(pageSso) at mounted cycle it showed pageSso was 0. What would be the problem?
store/store.js
export const state = () => ({
pageSso: 0,
})
export const getters = {
pageSso: (state) => state.pageSso,
}
export const mutations = {
setPageSso(state, data) {
console.log('mutations setPageSso data', data)
state.pageSso = data
}
}
export const actions = {
setPageSso({
commit
}, data) {
console.log('actions setPageSso data', data)
commit('setPageSso', data)
},
}
pages/login.vue
<template>
<input
class="checkbox_sso"
type="checkbox"
v-model="sso"
true-value="1"
false-value="0" >SSO checkbox
<button class="point" #click="submit">login</button>
</template>
<script>
export default {
data() {
return {
sso: '',
}
},
computed: {},
methods: {
submit() {
this.$store.dispatch('store/setPageSso', this.sso)
//this.$store.dispatch('store/login', data)
},
</script>
pages/dashboard.vue
<template>
<div class="base flex">
<Lnb />
<div class="main">
<Gnb />
<nuxt-child />
</div>
</div>
</template>
<script>
import Lnb from '#/components/Lnb'
import Gnb from '#/components/Gnb'
export default {
components: {
Lnb,
Gnb
},
mounted() {},
}
</script>
components/Lnb.vue
<template>
<ul>
<li :class="{ active: navbarState == 7 ? true : false }">
<a href="/dashboard/settings">
<img src="../assets/images/ico_settings.svg" alt="icon" /> Settings
</a>
</li>
<li v-show="pageSso != 1" :class="{ active: navbarState == 8 ? true : false }">
<a href="/dashboard/user">
<img src="../assets/images/ico_user.svg" alt="icon" />
Account
</a>
</li>
</ul>
</template>
<script>
import {
mapState
} from 'vuex'
export default {
data() {
return {}
},
computed: {
...mapState('store', {
// navbarState: (state) => state.navbarState,
pageSso: (state) => state.pageSso,
}),
},
mounted() {
console.log('pageSso ->', this.pageSso);
},
methods: {
},
}
</script>
Your console.log(pageSso) logs 0 because the mounted hook of Lnb.vue happens once, and it happens when the component is inserted into the DOM.
You insert Lnb into the DOM unconditionally in this line of dashboard.vue:
<Lnb />
and this is roughly when it's mounted hook is triggered.
Your pageSso seems to be changed only after you triggered the submit() method, which — I guess — happens way later, when you submit the login form.
Your Lnb.vue currently is always mounted. If you don't want to show it until pageSso is equal to 1, add a v-if on it in dashboard.vue like this:
<Lnb v-if="pageSso === 1" />
You currently don't have pageSso variable in dashboard.vue, you must take it from the store.
N.B.: Mind the difference between v-show and v-if directives: v-show only hides the component with display: none; while v-if actually removes or inserts the component from/to the DOM. With v-show, the component gets mounted even if you don't see it. With v-if, the component's mounted hook will fire each time the condition evaluates to true.

Vue modal component using in parent component

I'm building simple modal component with Vue. I want to use this component in a parent components and to be able to toggle it from the parent.
This is my code now of the modal component:
<script>
export default {
name: 'Modal',
data() {
return {
modalOpen: true,
}
},
methods: {
modalToggle() {
this.modalOpen = !this.modalOpen
},
},
}
</script>
<template>
<div v-if="modalOpen" class="modal">
<div class="body">
body
</div>
<div class="btn_cancel" #click="modalToggle">
<i class="icon icon-cancel" />
</div>
</div>
</template>
I use the v-if to toggle the rendering and it works with the button i created inside my modal component.
However my problem is: I don't know how to toggle it with simple button from parent component. I don't know how to access the modalOpen data from the modal component
Ok, let's try to do it right. I propose to make a full-fledged component and control the opening and closing of a modal window using the v-model in parent components or in other includes.
1) We need declare prop - "value" in "props" for child component.
<script>
export default {
name: 'Modal',
props: ["value"],
data() {
return {
modalOpen: true,
}
},
methods: {
modalToggle() {
this.modalOpen = !this.modalOpen
},
},
}
</script>
2) Replace your "modalToggle" that:
modalToggle() {
this.$emit('input', !this.value);
}
3) In parent components or other includes declare "modal=false" var and use on component v-model="modal" and any control buttons for modal var.
summary
<template>
<div v-if="value" class="modal">
<div class="body">
body
</div>
<div class="btn_cancel" #click="modalToggle">
<i class="icon icon-cancel" />
</div>
</div>
</template>
<script>
export default {
name: "Modal",
props: ["value"],
methods: {
modalToggle() {
this.$emit("input", !this.value);
}
}
};
</script>
Example:
Vue.component('modal', {
template: '<div v-if="value" class="modal"><div class="body">modal body</div><div class="btn_cancel" #click="modalToggle">close modal<i class="icon icon-cancel" /></div></div>',
props: ["value"],
methods: {
modalToggle() {
this.$emit('input', !this.value);
}
}
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app',
data:() =>({
modal: false
}),
methods: {
openModal() {
this.modal = !this.modal;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div #click="openModal">Btn modal</div>
<modal v-model="modal"></modal>
</div>
With your current implementation I would suggest you to use refs
https://v2.vuejs.org/v2/guide/components-edge-cases.html#Accessing-Child-Component-Instances-amp-Child-Elements
So in your parent component add ref="child" to modal (child) component and then open your modal by calling this.$refs.child.modalToggle()
You can use an "activator" slot
You can use ref="xxx" on the child and access it from the parent

Move elements passed into a component using a slot

I'm just starting out with VueJS and I was trying to port over a simple jQuery read more plugin I had.
I've got everything working except I don't know how to get access to the contents of the slot. What I would like to do is move some elements passed into the slot to right above the div.readmore__wrapper.
Can this be done simply in the template, or am I going to have to do it some other way?
Here's my component so far...
<template>
<div class="readmore">
<!-- SOME ELEMENTS PASSED TO SLOT TO GO HERE! -->
<div class="readmore__wrapper" :class="{ 'active': open }">
<slot></slot>
</div>
Read {{ open ? lessLabel : moreLabel }}
</div>
</template>
<script>
export default {
name: 'read-more',
data() {
return {
open: false,
moreLabel: 'more',
lessLabel: 'less'
};
},
methods: {
toggle() {
this.open = !this.open;
}
},
}
</script>
You can certainly do what you describe. Manipulating the DOM in a component is typically done in the mounted hook. If you expect the content of the slot to be updated at some point, you might need to do the same thing in the updated hook, although in playing with it, simply having some interpolated content change didn't require it.
new Vue({
el: '#app',
components: {
readMore: {
template: '#read-more-template',
data() {
return {
open: false,
moreLabel: 'more',
lessLabel: 'less'
};
},
methods: {
toggle() {
this.open = !this.open;
}
},
mounted() {
const readmoreEl = this.$el.querySelector('.readmore__wrapper');
const firstEl = readmoreEl.querySelector('*');
this.$el.insertBefore(firstEl, readmoreEl);
}
}
}
});
.readmore__wrapper {
display: none;
}
.readmore__wrapper.active {
display: block;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id='app'>
Hi there.
<read-more>
<div>First div inside</div>
<div>Another div of content</div>
</read-more>
</div>
<template id="read-more-template">
<div class="readmore">
<!-- SOME ELEMENTS PASSED TO SLOT TO GO HERE! -->
<div class="readmore__wrapper" :class="{ 'active': open }">
<slot></slot>
</div>
Read {{ open ? lessLabel : moreLabel }}
</div>
</template>