Vue Component: how to pass data from parent to child - vue-component

how can i access the doctor attribute from my doctor component ?
Vue.component('specialists', {
template: `
<div>
<div class="list-group-item title">
» {{ name }}
</div>
<doctor class="list-group-item" v-for="doctor in doctors">
<a href="#" class="list-group-item-heading">
{{ doctor.full_name }}
</a>
</doctor>
</div>
`,
props: {
name: {
default: '',
},
},
data: function() {
return {
algoliaClient: null,
algoliaIndex: null,
doctors: [],
};
},
created: function() {
this.algoliaClient = this.$parent.algoliaClient;
this.algoliaIndex = this.algoliaClient.initIndex('medical_doctors');
},
mounted: function() {
this.getDoctors();
},
methods: {
getDoctors: function() {
this.search(this.name);
},
search: function(input) {
var _this = this;
this.algoliaIndex.search(this.name, function(err, hits) {
_this.setDoctors(hits.hits);
});
},
setDoctors: function(data) {
this.doctors = data;
},
},
});
// my doctor component
Vue.component('doctor', {
template: `
<div><slot></slot></div>
`,
data: function() {
return {
doctor: null, // here. how can i pass value to it?
};
},
});
How can i access the doctor attribute from my specialists component ?
I've tried accessing the this.$children from specialists component but the child is null

I'd try something like this :
Vue.component('specialists', {
template: `
<div>
<div class="list-group-item title">
» {{ name }}
</div>
<doctor class="list-group-item" v-for="doctor in doctors" :doctor="doctor">
<a href="#" class="list-group-item-heading">
{{ doctor.full_name }}
</a>
</doctor>
</div>
`,
props: {
name: {
default: '',
},
},
data: function() {
return {
algoliaClient: null,
algoliaIndex: null,
doctors: [],
};
},
created: function() {
this.algoliaClient = this.$parent.algoliaClient;
this.algoliaIndex = this.algoliaClient.initIndex('medical_doctors');
},
mounted: function() {
this.getDoctors();
},
methods: {
getDoctors: function() {
this.search(this.name);
},
search: function(input) {
var _this = this;
this.algoliaIndex.search(this.name, function(err, hits) {
_this.setDoctors(hits.hits);
});
},
setDoctors: function(data) {
this.doctors = data;
},
},
});
// my doctor component
Vue.component('doctor', {
template: `
<div><slot></slot></div>
`,
props: {
doctor: {
default: '',
}
}
});
passing :doctor="doctor" in the for loop of the parent
and adding doctor to props in the child component
props: {
doctor: {
default: '',
},
},

Related

Can I load the component template dynamically

Is it possible to load the template from server (include components) dynamically? Or can I change the template before it rendered?
I would like to let user to store their own form template into a database and generate the form according to the template-id.
I tried to change the this.$options.template, but it seems like only work for vue2.
<!-- static/myproj/js/my-field.vue -->
<template>
<label :for="name+'Fld'" v-html="title"></label>
<input :name="name" :type="text" :value="value" :id="name+'Fld'"/>
</template>
<script>
export default{
props: {
name: {type:String, required:true},
value: {type:String, required:false, default:''},
type: {type:String, required:true},
title: {type:String, required:false, default:'Field: '},
},
data: function(){ return {}; },
}
</script>
// index.vue
const loadVueModuleOpts= {
moduleCache: {vue: Vue},
async getFile(url) {
const res = await fetch(url);
if ( !res.ok )
throw Object.assign(new Error(res.statusText + ' ' + url), { res });
return {
getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
}
},
};
export default{
props: {
id: {required:true, type:String, default:'abcdefg'},
},
data: function(){
this.loadSource();
return {
source: null,
target: null,
};
},
template: '<div>I\'m here to be replaced.</div>',
created: async function(){
this.$options.template=await axios.get(`/api/template/${id}`).then(resp=>resp.data);
},
components: {
'my-field': Vue.defineAsyncComponent( ()=>loadModule('/static/myproj/js/my-field.vue', loadVueModuleOpts)),
}
<!-- server response for /api/template/abcdefg -->
<form action="POST">
<my-field name="name" title="Your Name: " type="text"/>
<my-field name="email" title="Email: " type="email"/>
<input type="submit"/><input type="reset"/>
</form>
Thanks.
Finally, I got the solution. According to Vue3: How to use Vue.compile in Vue3, we can render the template directly by Vue3 like this:
// index.vue
import { h, compile } from 'vue';
const loadVueModuleOpts= {
moduleCache: {vue: Vue},
async getFile(url) {
const res = await fetch(url);
if ( !res.ok )
throw Object.assign(new Error(res.statusText + ' ' + url), { res });
return {
getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
}
},
};
export default{
props: {
id: {required:true, type:String, default:'abcdefg'},
},
data: function(){
this.loadSource();
return {
source: null,
target: null,
};
},
// Magic here
render: function(){
if(this.target)
return h(compile(this.target).bind(this));
return h('div', 'Loading...');
},
created: async function(){
this.$options.template=await axios.get(`/api/template/${id}`).then(resp=>resp.data);
},
components: {
'my-field': Vue.defineAsyncComponent( ()=>loadModule('/static/myproj/js/my-field.vue', loadVueModuleOpts)),
},
}

Vue3- Vuetify - Infinite scroll like instagram/facebook

I'm developing a website that I want to add a infinite scroll feature.
This site is currently using vue3 and vuetify next.
The following code is a component with a default slot.(It's working, but not as I wanted)
Component
<template>
<section>
<slot />
<VRow v-if="data?.loadMoreSection?.showLoadMoreSection" class="mt-10" no-gutters>
<VContainer fluid>
<VRow
v-if="data?.loadMoreSection?.showScrollDownOrLoadMoreText && !data.delayScroll"
>
<VSpacer />
<VCol cols="auto">
<span v-if="data?.loadMoreSection?.loadMoreType">
{{
getText({
...(data?.loadMoreSection?.scrollDownOrLoadMoreText ?? {}),
params: { type: getText(data?.loadMoreSection?.loadMoreType) },
})
}}
</span>
<span v-else>
{{ getText(data?.loadMoreSection?.scrollDownOrLoadMoreText) }}
</span>
</VCol>
<VSpacer />
</VRow>
<VRow v-if="data?.loadMoreSection?.showLoadMoreButton">
<VSpacer />
<VCol cols="auto">
<VBtn #click.stop="onLoadMore">
<span v-if="data?.loadMoreSection?.loadMoreType">
{{
getText({
...(data?.loadMoreSection?.loadMoreButtonText ?? {}),
params: { type: getText(data?.loadMoreSection?.loadMoreType) },
})
}}
</span>
<span v-else>
{{ getText(data?.loadMoreSection?.loadMoreButtonText) }}
</span>
</VBtn>
</VCol>
<VSpacer />
</VRow>
</VContainer>
</VRow>
</section>
</template>
<script setup lang="ts">
import { Events } from "#enums";
import { ILocaleText } from "#models/localeText";
export interface IInfiniteScrollLoaderEvents {
(e: Events.onScroll): void;
(e: Events.onLoadMore): void;
(e: Events.onScrollDelayed): void;
}
export interface IInfiniteScrollLoadMoreSectionProperties {
showLoadMoreSection?: boolean;
showLoadMoreButton?: boolean;
showScrollDownOrLoadMoreText?: boolean;
scrollDownText?: ILocaleText;
loadMoreButtonText?: ILocaleText;
loadMoreType?: ILocaleText;
scrollDownOrLoadMoreText?: ILocaleText;
}
export interface IInfiniteScrollLoaderProperties {
padding?: number;
scrollTimeoutMs?: number;
disableScroll?: boolean;
loadMoreSection?: IInfiniteScrollLoadMoreSectionProperties;
}
interface IInfiniteScrollLoaderData {
delayScroll: boolean;
loadMoreSection?: IInfiniteScrollLoadMoreSectionProperties;
}
const properties = withDefaults(defineProps<IInfiniteScrollLoaderProperties>(), {
padding: 50,
disableScroll: false,
scrollTimeoutMs: 1000,
});
const data = reactive<IInfiniteScrollLoaderData>({
delayScroll: false,
loadMoreSection: {
showLoadMoreSection: true,
showLoadMoreButton: true,
showScrollDownOrLoadMoreText: true,
loadMoreButtonText: {
key: "templates.loadMoreTypeEllipsis",
},
scrollDownOrLoadMoreText: {
key: "templates.scrollDownOrClickToLoadMoreTypeEllipsis",
},
},
});
onMounted(async () => {
await mergeLoadMoreSectionPropertiesWithData();
});
async function mergeLoadMoreSectionPropertiesWithData() {
const merged = {};
Object.assign(merged, data.loadMoreSection, properties.loadMoreSection);
data.loadMoreSection = merged;
}
const emits = defineEmits<IInfiniteScrollLoaderEvents>();
const isValidScroll = computed(
() =>
window.scrollY + window.innerHeight >= document.body.scrollHeight - properties.padding
);
function delayScroll() {
data.delayScroll = true;
setTimeout(() => {
data.delayScroll = false;
emits(Events.onScrollDelayed);
}, properties.scrollTimeoutMs ?? 1000);
}
function isScrollingDown(event: any) {
if (!event) return false;
return !(event.deltaY && event.deltaY < 0);
}
function onLoadMore(event: any) {
emits(Events.onLoadMore);
}
function onScroll(event: any) {
if (properties.disableScroll) return;
if (!isScrollingDown(event)) return;
if (!isValidScroll) return;
if (data.delayScroll) return;
emits(Events.onScroll);
delayScroll();
}
onMounted(async () => {
if (!properties.disableScroll) window.addEventListener("wheel", onScroll);
});
onUnmounted(async () => {
if (!properties.disableScroll) window.removeEventListener("wheel", onScroll);
});
</script>
Usage Example
<template>
<InfiniteScrollLoader
#on-scroll="fetchData"
#on-load-more="fetchData"
:scroll-timeout-ms="5000"
:disable-scroll="data.loading"
:loadMoreSection="props.loadMoreSection"
>
<div>content</div>
</InfiniteScrollLoader>
</template>
<script setup lang="ts">
async function fetchData(){
await setTimeout(()=>console.log('fetched'),5000)
}
</script>
I just want to make this component more smooth. But I'm terrible working with css/animations and etc, I don't have any idea how to implement it.

vuejs lifecycle hooks mounted push computed

I want to send the data from axios in mounted to the menuItem function in computed, but I have not been successful. Where am I doing wrong?
<template v-slot:MenuItem>
<MenuItems
v-for="(Item, Index) in menuItem"
:key="Index"
:items="Item"
:depth="Index"
>
<router-link :to="Item.path">{{ Item.name }}</router-link>
</MenuItems>
</template>
<script>
export default {
name: 'Nav',
data() {
return {}
},
computed: {
menuItem() {
return [
{
name: this.$t('navbar.home'),
path: '/',
},
{
name: this.$t('navbar.gallery'),
path: '/gallery',
},
{
name: this.$t('navbar.contact'),
path: '/contact',
},
]
},
},
async mounted() {
const res = await axios.get('http://localhost:3000/categories')
if (res.status === 200) {
const arr = res.data.data
arr.forEach((value, index) => {
this.menuItem.push({
name: value.title,
path: '/' + value.slug,
})
})
}
},
}
</script>

How do I display a route parameter (Vue / Firebase)

I'm creating a blog with free sewing patterns as content. I'm using route parameters to receive each blog individually. However, I'm getting a blank page when trying to retrieve its data from firebase firestore. Please help.
The blog's id appears on my address bar:
http://localhost:8080/#/admin/single-pattern/4LIS362IEWa7RKEv79g8
But it renders a blank page. I cant see my blog content.
This is my route path code. I've added a parameter of :id in my singlepattern. The SinglePattern component is where I will get the individual blog's data:
{
path: "/admin",
name: "admin",
component: Admin,
meta: {
auth: true,
},
children: [
{
path: "dashboard",
name: "dashboard",
component: Dashboard,
},
{
path: "manage-patterns",
name: "manage-patterns",
component: ManagePatterns,
},
{
path: "single-pattern/:id",
name: "single-pattern",
component: SinglePattern,
},
],
},
Here is my "ListPattern" component's code. ListPattern is where all my sewing blogs are displayed.
<template>
<div class="list-blogs">
<h1>LIST BLOG TITLES</h1>
<br />
<input type="text" v-model="search" placeholder="search blogs" />
<div
class="blog-cover"
v-for="pattern in filteredPatterns"
:key="pattern.id"
>
<div>
<router-link v-bind:to="'/admin/single-pattern/' + pattern.id">
<h3 style="cursor: pointer" v-rainbow>
{{ pattern.title | uppercase }}
</h3></router-link
>
</div>
<p
:style="'background-color: var(--lightgrey)'"
:inner-html.prop="pattern.description | snippet"
></p>
</div>
</div>
</template>
<script>
import firebase from "firebase";
import searchMixin from "../mixins/searchMixin";
// Basic Use - Covers most scenarios
import { VueEditor } from "vue2-editor";
import Quill from "quill";
const AlignStyle = Quill.import("attributors/style/align");
Quill.register(AlignStyle, true);
// import $ from "jquery";
import Swal from "sweetalert2";
window.Swal = Swal;
const Toast = Swal.mixin({
toast: true,
position: "top-end",
showConfirmButton: false,
timer: 3000,
});
window.Toast = Toast;
export default {
name: "ManagePatterns",
components: { VueEditor },
data() {
return {
patterns: [],
pattern: {
title: null,
description: null,
image: null,
},
search: "",
};
},
firestore() {
return {
patterns: firebase.firestore().collection("free-patterns"),
};
},
computed: {},
},
};
</script>
And this is my 'SinglePattern' component where the clicked blog/pattern is displayed.
<template>
<div class="single-pattern">
<div class="blog-cover">
<div>
</div>
<div v-if="pattern">
<h3 style="cursor: pointer">
{{ pattern.title }}
</h3>
<div v-if="pattern.description">
<p
:style="'background-color: var(--lightgrey)'"
:inner-html.prop="pattern.description"
></p>
</div>
</div>
</div>
</div>
</template>
<script>
import firebase from "firebase";
import searchMixin from "../../mixins/searchMixin";
export default {
data() {
return {
id: this.$route.params.id,
patterns: [],
pattern: {
title: null,
description: null,
image: null,
},
};
},
firestore() {
return {
patterns: firebase.firestore().collection("free-patterns"),
};
},
mixins: [searchMixin],
created() {
console.log(this.$route.params.id);
var pat = this;
firebase
.firestore()
.collection("free-patterns")
.doc(this.$route.params.id)
.get()
.then(function(doc) {
if (doc.exists) {
pat.pattern = doc.data().pattern;
} else {
console.log("no such doc");
}
});
},
methods: {},
};
</script>
It works. I just had to change the code in my created() hook in 'SingePattern' component.
created() {
console.log(this.$route.params.id);
var docRef = firebase
.firestore()
.collection("free-patterns")
.doc(this.$route.params.id);
docRef
.get()
.then((doc) => {
if (doc.exists) {
this.pattern = doc.data();
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
})
.catch((error) => {
console.log("Error getting document:", error);
});

Vuex state change is not reactive

I am working with Vuex and Firebase Auth system.
I just want to store with Vuex the user object that i get from:
firebase.auth().getCurrentUser
so that every time it changes, it updates the views.
But i ve troubles with this.
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
user: {
loggedIn: false,
data: null
}
},
getters: {
user(state){
return state.user
}
},
mutations: {
SET_LOGGED_IN(state, value) {
state.user.loggedIn = value;
},
SET_USER(state, data) {
state.user.data = data;
}
},
actions: {
fetchUser({ commit }, user) {
commit("SET_LOGGED_IN", user !== null);
if (user) {
commit("SET_USER", user);
} else {
commit("SET_USER", null);
}
}
}
});
Account.vue
<template>
<ion-item>
<ion-label #click="openModal()" position="stacked">Your name</ion-label>
{{user.data.displayName}}
</ion-item>
</template>
computed: {
// map `this.user` to `this.$store.getters.user`
...mapGetters({
user: "user"
})
},
methods: {
openModal() {
let us = this.$store.getters.user;
return this.$ionic.modalController
.create({
component: Modal,
componentProps: {
data: {
content: 'New Content',
},
propsData: {
pro: us.data.displayName
},
},
})
.then(m => m.present())
},
.
.
.
</script>
Modal.vue
<template>
<ion-app>
<h1>MODAL</h1>
<ion-input :value="prop" #input="prop = $event.target.value"></ion-input>
<ion-button #click="clos()">Save</ion-button>
</ion-app>
</template>
<script>
import firebase from "firebase";
export default {
props:['pro'],
data(){
return{
prop: this.pro
}
},
methods:{
clos(){
let vm = this;
let user = firebase.auth().currentUser;
window.console.log("updating",vm.prop)
user.updateProfile({
displayName: vm.prop
}).then(function(){
user = firebase.auth().currentUser;
vm.$store.dispatch("fetchUser",user);
}).catch(function(err){
window.console.log("err",err);
})
this.$ionic.modalController.dismiss();
}
}
}
</script>
I can see using Vue Dev Tools that when I dispatch the new user in Modal.vue
vm.$store.dispatch("fetchUser",user);
that the Vuex state is correctly updated, but the view in Account.vue is not.
But if I press the button 'commit this mutation' in the dev tools the view updates!
How can I fix this behavior?
try this solution:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
user: {
loggedIn: false,
data: null
}
},
getters: {
user(state){
return state.user;
},
userData(state){
return state.user.data;
}
},
mutations: {
SET_LOGGED_IN(state, value) {
state.user.loggedIn = value;
},
SET_USER(state, data) {
state.user.data = data;
}
},
actions: {
fetchUser({ commit }, user) {
commit("SET_LOGGED_IN", user !== null);
if (user) {
commit("SET_USER", user);
} else {
commit("SET_USER", null);
}
}
}
});
Account.vue
<template>
<ion-item>
<ion-label #click="openModal()" position="stacked">Your name</ion-label>
{{user.displayName}}
</ion-item>
</template>
computed: {
// map `this.user` to `this.$store.getters.user`
...mapGetters({
user: "userData"
})
},
methods: {
openModal() {
let us = this.$store.getters.userData;
return this.$ionic.modalController
.create({
component: Modal,
componentProps: {
data: {
content: 'New Content',
},
propsData: {
pro: us.displayName
},
},
})
.then(m => m.present())
},
.
.
.
</script>
You can try this:
SET_USER(state, data) {
Vue.$set(state.user, 'data', data)
}

Resources