How can you pass props into a modal dialog in vuejs? - vue.js

In a Vuejs app, I have a modal dialog box pop up when user clicks a button. I need to pass a prop into this modal component. Normally, I have no trouble passing props to child components. Why is this case different? I'd rather not store any data in the store if avoidable. When the modal opens the prop collaboratorSelected is an empty String, i.e. it does not register the new prop value the parent is passing, but instead keeps the initial value set when the parent component mounts. This prop needs to be responsive.
Parent component...
<template>
<div>
<div v-for="collaborator in collaborators">
Edit
</div>
<EditShare #editedShare="editedShare" :collaboratorSelected="collaboratorToEdit" ref="modal" ></EditShare>
</div>
</template>
<script>
import axios from "axios";
import store from "../store.js";
import EditShare from "#/components/EditShare";
import $ from "jquery";
export default {
name: "Share",
data() {
return {
collaboratorToEdit: "",
collaborators: ["1", "2", "3"]
};
},
components: {
EditShare
},
methods: {
showEditShare(collaborator) {
this.collaboratorToEdit = collaborator;
let element = $('#editShare');
$(element).modal('show');
}
}
</script>
Child modal component...
<template>
<form #submit.prevent="handleSubmit">
<div class="modal" tabindex="-1" role="dialog" id="editShare">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title card-title-bigger">Add Share</div>
{{collaboratorSelected}}
</div>
</div>
</div>
</div>
</form>
</template>
<script>
import axios from "axios";
import store from "../store.js";
import $ from "jquery";
export default {
name: "EditShare",
props: {
collaboratorSelected: String
},
</script>

I believe that given that you're using jQuery, the jQuery code is executing before the property value is propagated to the child component.
Try wrapping the jQuery code in a $nextTick method and see if that works. Something like this:
this.collaboratorToEdit = collaborator;
this.$nextTick(function(){
let element = $('#editShare');
$(element).modal('show');
});
I did some experiments in this pen and I was able to see how the code in the function doesn't wait for the property value to propagate (as you have it) and how it waits for it when using $nextTick.
Let me know if it works.

Related

Vue.js - Emit from grandchild slot component didn't bubble $emit event to parent

I planned and made a modal and then created a button to close the modal window.
I wanted to change the value of isHomeDialog using $emit as an event of the button.
However, $emit's event was not delivered to "Home.vue"
Home.vue
<template>
<div>
<ReviewDialog
:is-open="isHomeDialog"
#close="closeEvent()/>
</div>
</template>
<script>
import ReviewDialog from '#/components/popup/dialog/ReviewDialog';
</script>
export default {
name: 'Home',
components: {
ReviewDialog
},
data () {
return {
isHomeDialog: true
};
},
methods: {
closeEvent () {
console.log('close');
isHomeDialog = false;
}
}
};
BaseDialog.vue
<template>
<div v-show="isOpen">
<div class="mask"> </div>
<div class="content">
<button
class="close"
#click="$emit('close')"> </button>
<slot/>
</div>
</div>
</template>
<script>
export default {
props: {
isOpen: {
type: Boolean,
required: true
}
}
};
Reviewdialog.vue
<template>
<BaseDialog
url="#/components/popup/dialog/BaseDialog"
id="review-dialog"
:is-open="isOpen"
:header-text="'REVIEW'">
<div class="warp">
<p>
test
</p>
</div>
</BaseDialog>
</template>
<script>
import BaseDialog from '#/components/popup/dialog/BaseDialog';
export default {
components: {
BaseDialog
},
props: {
isOpen: {
type: Boolean,
default: false
}
}
</script>
Home
└ BaseDialog
 └ ReviewDialog
In the above structure, I tried to send a request to BaseDialog and ReviewDialog with $emit, but it was not delivered to Home.
Is there any way to send $ emit to its parent component other than requesting it with $root.$emit?
BaseDialog emits the close event to its parent, ReviewDialog, which is not listening for it. So you need to listen in ReviewDialog, and re-emit the event to Home:
<BaseDialog
url="#/components/popup/dialog/BaseDialog"
id="review-dialog"
:is-open="isOpen"
:header-text="'REVIEW'"
#close="$emit('close')"> <-- This line is new -->
Another way to do this is to have ReviewDialog pass all its listeners down:
<BaseDialog
url="#/components/popup/dialog/BaseDialog"
id="review-dialog"
:is-open="isOpen"
:header-text="'REVIEW'"
v-on="$listeners"> <-- This line is new -->
Since two-way binding is deprecated in Vue2 + child cannot mutate props directly
Likely another approach custom component with v-model
I put reference below:
vuejs update parent data from child component

How to get $auth value in component outside of registered vue-router component

I have a layout theme/default which has vue-router inside like this
<template>
<div id="app">
<component :is = "layout">
<router-view></router-view>
</component>
</div>
</template>
<script>
const default_layout = "theme";
export default {
computed: {
layout(){
return ( this.$route.meta.layout || default_layout) + '-layout';
}
},
};
</script>
And then the theme layout is like this:
<template>
<div class="app-home">
<nav-bar/>
<div class="container-fluid section">
<div class="left-fixed">
<side-bar/>
</div>
<div class="right-card">
<slot />
</div>
</div>
</div>
</template>
<script>
import NavBar from './Navbar'
import SideBar from './Sidebar'
export default {
data() {
return {
}
},
mounted(){
},
components: {
NavBar,
SideBar
}
}
</script>
Now I have to pass current auth user in Navbar and Sidebar for logout and current user role which can be obtained from vue-auth $auth but only inside router component. Can anybody help it to fix this.
Using vuex I had made a state which I call as computed property and I set whenever User logged in.

How to set $ref to child component elements from parent component in vuejs?

This is the parent component:
<template>
<upload-block
:imSrc="LargeIcon"
inputName="LargeIcon"
:inputHandler="uploadAppIcon"
inputRef="LargeIcon"
:uploadClickHandler="handleUploadIcon"></upload-block>
</template>
<script>
export default class ParentCom extends Vue {
//all props for <upload-block></upload-block> component defined here
handleUploadIcon(event) {
const icon_type = event.currentTarget.getAttribute("data-type");
let appImgElem = this.$refs[icon_type];
appImgElem.click();
}
async uploadAppIcon(event) {
//code
}
}
</script>
And this is the child component:
<template>
<div class="upload-div" #click="uploadClickHandler" :data-type="inputName">
<img v-if="imSrc" :src="imSrc">
<div v-else class="upload-icon-block">
<span>
<font-awesome-icon class="upload-icon" icon="arrow-circle-up" size="lg"></font-awesome-icon>
<br>Click to upload
</span>
</div>
<!-- <spinner variant="primary" :show="true"></spinner> -->
<input style="display:none" type="file" :ref="inputRef" :name="inputName" #input="inputHandler">
</div>
</template>
<script>
#Component({
props: {
imSrc: String,
inputRef: String,
inputName: String,
inputHandler: Function,
uploadClickHandler: Function
}
})
export default class ChicdCom extends Vue {
}
</script>
The problem I am facing in the handleUploadIcon method in which I am not able to get the input element via ref.
It is showing Cannot read property 'click' of undefined in this line appImgElem.click();
But when I move the file input to the parent component, it's works fine. So can you plz help me how to set the ref to child component elements from parent as currently is it not setting.
Thanks
Well you could add a ref to upload-block in the parent component:
<upload-block ref="upload" ... >
Then in the handleUploadIcon you can acces your input: this.$refs.upload.$refs[icon_type]
But I would try to move handleUploadIcon to the child component if I were you.

How to access parents data value in vuejs with the template syntax

In my VueJS app I am using bootstrap-vue and want to use iframe inside a collapsable elements b-collapse. Because of some problems with iframe content and resizing (problem not related here) I found out that if I enable/disable b-embed with conditional rendering it works.
The parent component b-collapse has a data element called show which change its state if toggle is clicked. In my HelloWorld component I want that b-collapse can pass it's show value into the if check of b-embed.
My approach with this.$parent.$data.show isn't working and I am not sure if there is any better way to do so.
<template>
<div>
<b-btn v-b-toggle.logs>
<span class="when-opened">Close</span>
<span class="when-closed">Open</span>
Trace
</b-btn>
<b-collapse id="logs">
<b-embed :src="src" v-if="this.$parent.$data.show"></b-embed>
<div>Data: {{this.$parent.$data.show}}</div>
</b-collapse>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import { Prop, Component, Inject } from "vue-property-decorator";
#Component
export default class HelloWorld extends Vue {
src = "http://localhost:7681/";
}
</script>
Like this:
parent.vue
<template>
<Child :show="myShow"></Child>
</template>
<script>
import Child from './child'
export default {
data () {
return {
myShow: 'StackOverflow'
}
},
components: {Child}
}
</script>
child.vue
<div>
<b-btn v-b-toggle.logs>
<span class="when-opened">Close</span>
<span class="when-closed">Open</span>
Trace
</b-btn>
<b-collapse id="logs">
<b-embed :src="src" v-if="this.$parent.$data.show"></b-embed>
<div>Data: {{this.$parent.$data.show}}</div>
</b-collapse>
</div>
<script>
export default {
props: {
show: {
type: Number,
require: true
}
}
}
</script>
Or use vuex to do this.

render a Vue slot in a layout from component

I'm having problems with a named slot. This seems like it should work. In the code below I'm trying to use a named slot "sidebar". I would expect my sidebar slot content to show up between the Sidebar starts and Sidebar ends text but nothing shows up in that slot. Everything renders in the main slot.
Here's my code.
route...
{
path: "/test",
name: "test",
meta: {
layout: "test-layout"
},
component: () =>
import("#/pages/Test.vue")
},
and App.vue template...
<template>
<div id="app">
<component :is="layout">
<router-view />
</component>
</div>
</template>
and test-layout...
<template>
<div>
<div>
<h1>Sidebar starts</h1>
<slot name="sidebar"/>
<h1>Sidebar ends</h1>
</div>
<div class="container">
<h1>Content starts</h1>
<slot/>
<h1>Content ends</h1>
</div>
</div>
</template>
and page Test.vue...
<template>
<test-layout>
<span slot="sidebar">sidebar slot content {{forecast.todaySummary}}</span>
<div>main slot content {{forecast.currentSummary}}</div>
</test-layout>
</template>
<script>
import api from "#/js/web-services";
export default {
data() {
return {
forecast: null
};
},
created() {
api.getDailyForecast().then(response => {
this.forecast = response.data;
});
}
};
</script>
and the import in my main.js
import TestLayout from "./layouts/test-layout.vue";
Vue.component('test-layout', TestLayout);
Why isn't my sidebar slot working?
UPDATE
If I get rid of the two lines in main.js and add
import TestLayout from "#/layouts/test-layout.vue";
and
export default {
components: { TestLayout },
data() {...
to Test.vue then it works.
In your router file you are using layout: "test-layout" this means what ever comes in your vue component will be rendered in base test-layout.
There are two ways as far as I know to render the layouts.
Do not define layout in router file and on parent component define named slots like this<slot #header></slot><slot #body></slot> then every slot will be rendered within this (test-layout) layout, and then in your each component extend like this <test-layout><template header>Header content</template><template body>Body content</template></test-layout>.
Defining layout in router file like you did, you can not further use in slots in that layout, you can just import other components e.g <template><Component1><Component2> </template>