Here is my setup:
Vue 2.5.16
Veux
Vue router
I have setup a simple router view that looks for a child component inside a parent one and the url structure is;
/folders/parent-uuid/child-uuid
I have a component for the parent below:
<template lang="html">
<div>
<!-- Articles -->
<div class="flex-none w-100 mv3 gray"><p>Bookmarks({{ subFolders.contentDetails.articles.length }})</p></div>
<div class="flex flex-row flex-wrap justify-start items-start">
<div v-for="article in subFolders.contentDetails.articles" class="pointer article-item relative mb4">
<a v-on:click.stop.prevent="checkFolder(article.uuid)" :class="[{highlight:selectedItems.includes(article.uuid)}, 'absolute w-100 h-100 left-0 top-0 highlight-area z-3']" href="#"><div class="absolute top-2" style="width: 18px;height: 18px;right: 3.05rem;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path v-if="selectedItems.includes(article.uuid)" fill="#ea792e" d="M3 17.2V21h3.8l11-11.1L14 6.1 3 17.2zM20.7 7c.4-.4.4-1 0-1.4l-2.3-2.3c-.4-.4-1-.4-1.4 0l-1.8 1.8L19 8.9c-.1 0 1.7-1.9 1.7-1.9z"/><path v-else fill="#ffffff" d="M3 17.2V21h3.8l11-11.1L14 6.1 3 17.2zM20.7 7c.4-.4.4-1 0-1.4l-2.3-2.3c-.4-.4-1-.4-1.4 0l-1.8 1.8L19 8.9c-.1 0 1.7-1.9 1.7-1.9z"/></svg></div></a>
<a :class="[{active:selectedItems.includes(article.uuid)}, 'link']" v-bind:href="article.contentUrl">
<div :class="[{active:selectedItems.includes(article.uuid)}, 'contentImage br3 overflow-hidden']">
<img class="w-100" :src=article.titleImage data-flickity-lazyload="article.titleImage">
</div>
<div v-if="article.contentType == 'Behaviour'" class="black">
<h4 class="f3">{{article.contentTitle}}</h4>
</div>
<div v-else class="black">
<h4 class="f3">{{article.contentTitle}}</h4>
</div>
<div v-if="article.contentType == 'Behaviour'">
</div>
<div v-else class="content no-margin">
<p class="f3">{{article.savedDate | moment("D MMM YYYY")}}</p>
</div>
</a>
</div>
</div>
<!-- end v-if -->
<div class="flex-none w-100 mt3 gray folders"><div class="ph5"><p>Subfolders({{ subFolders.subFolders.length }})</p></div></div>
<div class="flex flex-row flex-wrap justify-start items-start">
<div class="folder-item folder z-0 pointer">
<div class="relative db h-100 bg-light-gray folder-new br3 mb4">
<div v-on:click="addFolder($event)" class="top aspect-ratio--object">
<div class="dt w-100 h-100 no-margin showText pv6">
<div class="dtc v-mid tc ">
<svg class="dib v-mid icon-sprite" viewBox="0 0 38.89 38.89" width="40" height="40">
<circle cx="19.45" cy="19.45" r="19.45" fill="#ea792e"/><path stroke="#ffffff" stroke-width="3" d="M19.45 12.26v14.37M12.26 19.45h14.37" />
</svg>
<p>Create new folder</p>
</div>
</div>
</div>
<div class="bottom aspect-ratio--object">
<div class="dt w-100 h-100 ">
<div class="dtc v-mid tc no-margin showText pv6">
<p>Explore the library and add to this folder</p>
<form id="submit-folder" #submit.prevent="sendForm" class="mv3">
<input type="text" name="folderName" value="" placeholder="Add folder name in here..." v-model="folderName">
<input class="db center input-reset bg-orange b--none br2 white mv3 f3 pv3 ph4" style="font-family:'Open Sans',Helvetica Neue,sans-serif;" type="submit" name="Submit">
</form>
</div>
</div>
</div>
<div style="visibility: hidden;" class="image-blocks br3 overflow-hidden relative flex flex-wrap">
<div><img src="http://***/static-assets/images/EMPTY-FOLDER-GREY-1.jpg" alt=""></div>
<div><img src="http://***/static-assets/images/EMPTY-FOLDER-GREY-2.jpg" alt=""></div>
<div><img src="http://***/static-assets/images/EMPTY-FOLDER-GREY-3.jpg" alt=""></div>
</div>
</div>
</div>
<div v-for="folder in subFolders['subFolders']" class="pointer folder folder-item relative">
<a v-on:click.stop.prevent="checkFolder(folder.uuid)" :class="[{highlight:selectedItems.includes(folder.uuid)}, 'absolute w-100 h-100 left-0 top-0 highlight-area z-3']" href="#"><div class="absolute top-2" style="width: 18px;height: 18px;right: 3.05rem;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path v-if="selectedItems.includes(folder.uuid)" fill="#ea792e" d="M3 17.2V21h3.8l11-11.1L14 6.1 3 17.2zM20.7 7c.4-.4.4-1 0-1.4l-2.3-2.3c-.4-.4-1-.4-1.4 0l-1.8 1.8L19 8.9c-.1 0 1.7-1.9 1.7-1.9z"/><path v-else fill="#ffffff" d="M3 17.2V21h3.8l11-11.1L14 6.1 3 17.2zM20.7 7c.4-.4.4-1 0-1.4l-2.3-2.3c-.4-.4-1-.4-1.4 0l-1.8 1.8L19 8.9c-.1 0 1.7-1.9 1.7-1.9z"/></svg></div></a>
<router-link :class="[{active:selectedItems.includes(folder.uuid)}, 'z-0 db relative link']" :to="`/folders/${folderUUID}/${folder.uuid}`">
<div class="image-blocks br3 overflow-hidden relative">
<div class="" v-for="image in folder.topThreeThumbnails">
<img class="" :src=image>
</div>
</div>
</router-link>
<div :class="[{active:selectedItems.includes(folder.uuid)}, 'content']">
<router-link :to="`/folders/${folder.uuid}`" :class="[{active:selectedItems.includes(folder.uuid)}, 'link black']">
<h3>{{folder.folderName}}</h3>
<p>{{folder.subFolderCount}} subfolders, {{folder.totalElements}} elements</p>
</router-link>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
name: 'subfolderListParent',
data() {
return {
selected: [],
selectedArticles: [],
folder: '',
folderName: '',
folderUUID: this.$route.params.uuid
}
},
mounted: function () {
this.$store.dispatch('LOAD_SUBFOLDERS_LIST',this.$route.params.uuid)
this.$store.dispatch('LOAD_FOLDERS_LIST')
this.$store.state.selectedItems = [];
this.$route.meta.title = this.$store.getters.openSubfolderTitle;
},
props: ['uuid','subfolderName'],
computed: {
...mapState([
'subFolders',
'allFolders',
'allArticles',
'selectedItems',
'selectedFolder'
]),
...mapGetters([
'openSubfolderTitle',
])
},
methods: {
checkFolder: function(item){
this.$store.dispatch('SELECT_FOLDER',item)
},
addFolder: function(event){
const target = event.currentTarget.parentNode,
top = target.children[0],
bottom = target.children[1];
if(bottom.classList.contains('active')){
top.classList.remove('hidden');
bottom.classList.remove('active');
} else {
top.classList.add('hidden');
bottom.classList.add('active');
}
},
sendForm: function(event){
this.$store.dispatch('CREATE_FOLDER',{'folder':this.folderName,'uuid':this.$route.params.uuid})
this.folderName = '';
},
setTitle: function(){
this.$route.meta.title = this.$store.getters.openSubfolderTitle;
}
},
watch: {
'$route': function(from, to) {
this.$route.meta.title = this.$store.getters.openSubfolderTitle;
}
}
}
</script>
This is a pretty straight forward template that looks for some data in vuex etc and puts it into the template. Where I am coming into issues is when I try to get the child component loaded from a nested view.
The router code is as follows:
{
path: '/folders/:uuid',
component: Folder,
name: 'subfolderListParent',
props: true,
meta: {
bcDynamic: true,
bcGetter: 'openSubfolderTitle', // <breadcrumb> will use this getter to get the user from vuex
bcLinkText: state => state, // once we have the user, we use this function to format the LinkText dnynamically,
bcLoadingText: 'Loading title...' // This will be shown while Data is loading
},
children: [
{
path: '/:parentUUID',
props: true,
components: {
child: FolderChild
},
meta: {
bcDynamic: true,
bcGetter: 'openSubfolderTitle', // <breadcrumb> will use this getter to get the user from vuex
bcLinkText: state => state, // once we have the user, we use this function to format the LinkText dnynamically,
bcLoadingText: 'Loading title...' // This will be shown while Data is loading
},
}
]
},
And the app code where it all gets rendered is as follows:
<template>
<div id="app" style="clear:both;min-height:100vh;" class="flex-l flex-row flex-wrap items-stretch">
<transition name="slide-fade">
<div class="fixed right-2 br2 ph4 pv3 bg-orange white z-3" v-on:click="$store.state.noteText.note = !$store.state.noteText.note" v-bind:key="$store.state.noteText.note" v-if="$store.state.noteText.note === true">
<p class="white ma0 pa0 dib v-mid">{{$store.state.noteText.note}}</p>
</div>
</transition>
<MainNav/>
<div v-if="$route.path === '/from-canvas8'"></div>
<div class="flex-none" v-else>
<FolderActions/>
</div>
<div class="dib w-75-l w-100 v-top ph5 appWrapper">
<router-view></router-view>
</div>
</div>
</template>
<script>
import MainNav from './components/MainNav.vue'
import FolderList from './components/FolderList.vue'
import FolderActions from './components/FolderActions.vue'
import ScrapbookHome from './components/ScrapbookHome.vue'
import { mapState } from 'vuex'
export default {
components: {
MainNav,
FolderList,
FolderActions,
ScrapbookHome
},
name: 'scrapbook',
data () {
return {
welcomeMessage: 'Story Navigation'
}
},
methods: {
updateScroll: function(){
const nav = document.querySelector('.scrapbook-actions');
const navTop = nav.offsetTop;
function stickyNavigation() {
if (window.scrollY >= navTop) {
// nav offsetHeight = height of nav
nav.classList.add('fixed');
} else {
nav.classList.remove('fixed');
}
}
window.addEventListener('scroll', stickyNavigation);
},
},
mounted: function () {
// this.$store.dispatch('LOAD_FOLDERS_LIST')
//this.setNavHeight()
this.$store.dispatch('GET_USER')
},
watch: {
'$route': function(from, to) {
//this.updateScroll()
//this.setNavHeight()
}
}
}
</script>
For some reason the <router-view /> will not show this child component.
Can anyone help at all point me as to why this won't work?
When you nest routes, each route part will be mounted in the component of the parent route. In your case Folder will be mounted in App and FolderChild will be mounted in Folder. As far as I know, this is always the case with nested routes, even if you do not define a component for a particular sub-route. As far as I know there is not something like a fallthrough option that mounts a component in an applicable ancestor rather than the direct parent.
There are two ways to solve this problem. The easiest way is to not use child routes unless you have a shared boilerplate around each of the subroutes. You would get something like the following. You can somewhat organise your routes by using comments.
export default [
{
// some other routes
},
// Folder routes
{
path: '/folders/:uuid/:parentuuid',
component: FolderChild,
name: 'folderchild'
},
{
path: '/folders/:uuid',
component: Folder,
name: 'folders'
}
];
The other way is by still using the nested routes, but by putting in a dummy component with a single router view in parent routes where you do not have anything else to render.
// DummyView.vue
<template>
<router-view />
</template>
<script>
// Used to fill parent routes that need to have children mounted in them
export default {
name: 'dummy-view'
}
</script>
You then move your "default" route to a child route with an empty path and use this dummy view to render the parent route, and mount both Folder and FolderChild in this dummy view.
export default [
{
path: "/folders/:uuid",
component: DummyView,
children: [
{
path: "",
component: Folder
},
{
path: ":parentUUID",
component: FolderChild
}
]
}
];
This solution will become particularly aweful when you have multiple (named) views in a component, which all need to be filled for child views. The nice part is that if you have shared logic, you can use these steps to gradually build the view you want without needing to resort to these dummy components that only serve the purpose of having a place to mount child components.
Related
I have a sample Vue 3.0 project where, I have few child components with app.vue as parent component. I want to pass an array from app.vue component to list-product component which accepts an array but unable to do so. Code is as follows.
Error: On mount of list-product component, I am logging the input provided array from app.vue which turned out be a string and type check failed .
Stackbliz Link
app.vue
<template>
<Navbar title="FirstCry" />
<div class="row mt-3">
<div class="col-md-4 p-3 border bordered shadow-sm">
<Addproduct />
</div>
<div class="col-md-8">
<ListProduct products={{products}} />
</div>
</div>
<ViewProduct />
</template>
<script>
import Navbar from "./components/Navbar.vue";
import Addproduct from "./components/Addproduct.vue";
import ListProduct from "./components/list-product.vue";
import ViewProduct from "./components/View-Product.vue";
export default {
name: "App",
components: {
Navbar,
Addproduct,
ListProduct,
ViewProduct,
},
data() {
return {
products: [
{
name: "Iphone",
price: "22000",
image: "https://i.imgur.com/J9yBaqj.jpg",
}
]
}
}
};
</script>
<style>
</style>
list-products.vue
<template>
<div class="row">
<div class="col-md-4 product" >
<div class="card">
<div style="overflow: hidden;" class="card-header p-0">
<img class="card-img-top img-fluid" src='https://i.imgur.com/J9yBaqj.jpg' alt="Card image cap" style="height: 300;">
</div>
<div class="card-body">
<center><h5 class="card-title">Iphone</h5></center>
<p class="card-text text-secondary">
<!-- {{products[0].name}} -->
</p>
<center><h4>
<small>
<s class="text-secondary">
22
</s>
</small>
<span class="price">22</span>
</h4>
<a class="btn btn-warning ng-star-inserted" style="margin: 20px;">Add to Cart</a>
<a class="btn btn-primary">Buy Now</a></center>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "ListProduct",
props: {
title: String,
products: Array
},
mounted() {
console.log(this.products);
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.product {
margin-top: 10px;
}
.img-fluid:hover {
transform: scale(1.2);
overflow: hidden;
}
small {
font-size: 80%;
font-weight: 400;
}
.text-secondary {
color: #6c757d !important;
}
s {
text-decoration: line-through;
}
</style>
You should bind it using v-bind: or the shorthand syntax : :
<ListProduct :products="products" />
I have a web app which shows the blog posts in a grid. But the BlogCards component in the Home.vue just outputs nothing, whereas it should output the blogs in a grid format. All the datas are stored in firebase. If I go to /blogs, I can see the blogs in grid format, but it doesn't work on the Home.vue. It also spits out the Vue Warn: property or method "blogPostsCards" is not defined on the instance but referenced during render.
I took this code from this tutorial at 5:31:05 minute mark.
Any solution to this problem.
Home.vue
<template>
<div class="home">
<BlogPost :post="post" v-for="(post, index) in blogPostsFeed" :key="index" />
<div class="blog-card-wrap">
<div class="container">
<h3>View more recent blogs</h3>
<div class="blog-cards">
<BlogCards :post="post" v-for="(post, index) in blogPostsCard" :key="index" />
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import BlogPost from '../components/BlogPost.vue'
import BlogCards from '../components/BlogCards.vue'
export default {
name: "Home",
components: {
BlogPost,
BlogCards,
Arrow
},
computed : {
blogPostsCards() {
return this.$store.getters.blogPostsCards;
},
blogPostsFeed() {
return this.$store.getters.blogPostsFeed;
},
}
};
</script>
BlogCards.vue
<template>
<div class="blog-card">
<img :src="post.blogCoverPhoto" alt="">
<div class="info">
<h4>{{ post.blogTitle }}</h4>
<h6>Posted on: {{ new Date(post.blogDate).toLocaleString('en-us', {dateStyle: "long"})}}</h6>
<router-link class="link" to="#" >
View Post <Arrow class="arrow" />
</router-link>
</div>
</div>
</template>
<script>
export default {
name: "blogCard",
props: ["post"],
computed: {
editPost() {
return this.$store.state.editPost
},
}
}
</script>
And getter function in store/index.js
getters:{
blogPostsFeed(state){
return state.blogPosts.slice(0,2);
},
blogPostsCards(state) {
return state.blogPosts.slice(2,6);
},
},
<BlogCards :post="post" v-for="(post, index) in blogPostsCard" :key="index" />
In your Home.vue >> change blogPostsCard to blogPostsCards because you use blogPostsCards in your computed so it gives you that error.
In my project I use medium-zoom with pagination. On first page it works well but on second, third, fourth... I have to click several times for close image...on second page...two times, on third 3 times...
it seems to be a problem with maybe any index number?
Here is my code:
<template>
<div>
<div v-if="loading" class="text-center">
<i class="fas fa-spinner fa-pulse fa-5x"></i>
</div>
<div v-else>
<div v-for="(item, imageIndex) in pageOfItems" :key="item.id" class="m-3">
<div class="row mt-3">
<div class="col-lg-9 my-auto" v-html="item.mytext"></div>
<div class="col-lg-3 my-auto text-center">
<article class="container">
<img
class="img-thumbnail"
:src="'http://localhost:4000/api/galeria/' + item.galeriaId + '_f.jpg'"
/>
</article>
</div>
</div>
<hr class="hr1" />
</div>
</div>
<div class="pb-0 pt-3 text-center">
<jw-pagination :items="info" :page-size="10" #changePage="onChangePage"></jw-pagination>
</div>
</div>
</template>
<script>
import mediumZoom from 'medium-zoom'
import axios from 'axios'
export default {
data() {
return {
info: [],
customLabels,
pageOfItems: [],
loading: true,
}
},
mounted() {
axios
.get('http://localhost:4000/api/fetch_galeria.php/')
.then((response) => (this.info = response.data))
.finally(() => (this.loading = false))
},
updated() {
mediumZoom('article img', {
background: 'transparent',
})
},
methods: {
onChangePage(pageOfItems) {
// update page of items
this.pageOfItems = pageOfItems
},
},
}
</script>
You will pretty much need to detach the event listeners here. Because it should be a lot of them added everytime due to the updated hook.
A naive implementation would be to add mediumZoom on the mounted hook. And when you do have a new changePage event, to detach it from all the images, then to apply it to the new ones with the same mediumZoom call.
Below is a way to see which event listeners (and probably how many) you have linked to a specific VueJS component. Select it in the Vue devtools and then, you will have access to the element's properties via $vm0.
I'm working on this app and the idea is to show details of the cars in a sidebar on click. There are several issues like the sidebar is showing four times and I resolve it somehow but I don't know why is it showing four times. now I don't getting any response on emit call help me out please, I try $parent.$emit, $root.$emit but not seems working!!!
<template>
<div class="home">
<!-- warehouse details -->
<div
v-for="(detail, detailindex) in details"
:key="detailindex"
class="container mt-5 mb-5"
>
<h1>
{{ detail.name }}
<span class="location">{{ detail.cars.location }}</span>
</h1>
<!-- vehicle details -->
<SingleGarage :detail="detail"> </SingleGarage>
</div>
<b-sidebar
id="my-sidebar"
title="Sidebar with backdrop"
backdrop-variant="dark"
ref="mySidebar"
backdrop
shadow
#emitData="testingEmit()"
>
<div class="px-3 py-2">
<h1>{{currentCar}}</h1>
</div>
</b-sidebar>
</div>
</template>
<script>
// # is an alias to /src
import axios from "axios";
import SingleGarage from "../components/SingleGarage";
export default {
components: { SingleGarage },
name: "Home",
data: () => ({
details: String,
currentCar: 'String',
}),
methods:{
testingEmit(data){
this.currentCar = data
console.log('data from emit',data)
}
},
mounted() {
axios
.get("https://api.jsonbin.io/b/5ebe673947a2266b1478d892")
.then((response) => {
var results;
response.data.forEach((element) => {
element.cars.vehicles.sort((a, b) => {
a = new Date(a.date_added);
b = new Date(b.date_added);
results = a > b ? -1 : a < b ? 1 : 0;
return results * -1;
});
});
this.details = response.data;
});
},
};
</script>
<template>
<div class="vGrid mt-4">
<div
class="gridItem border vehicle singleCar"
v-for="(vehicle, vehicleIndex) in detail.cars.vehicles"
:class="'griditem' + vehicleIndex"
:key="vehicle._id"
>
<SingleCar
:vehicle="vehicle"
#click.native="testingTef(vehicleIndex)"
></SingleCar>
</div>
</div>
</template>
<script>
import SingleCar from "#/components/SingleCar";
export default {
name: "SingleGarage",
components: { SingleCar },
props: ["detail"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
currentCar : 1
}),
methods: {
testingTef(vehicleIndex) {
this.$parent.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)
this.$root.$emit('bv::toggle::collapse', 'my-sidebar')
console.log(this.detail.cars.vehicles[vehicleIndex].make)
console.log(this.detail.cars.vehicles[vehicleIndex].date_added)
this.currentCar = this.detail.cars.vehicles[vehicleIndex].make;
},
},
};
</script>
<template>
<div class="singleCar">
<!-- conditionally show image -->
<img
class="carImg"
:src="vehicle.img"
v-if="vehicle.img"
alt="No Preview"
/>
<img class="carImg" :src="dummyImg" v-else alt="No Preview" />
<div class="p-3">
<h3 class="make">{{ vehicle.make }}</h3>
<div class="modelDetails">
<div class="model d-flex ">
<p class="bold">Model:</p>
<p class="price ml-auto ">{{ vehicle.model }}</p>
</div>
<div class="price d-flex ">
<p class="bold">Price:</p>
<p class="price ml-auto ">€{{ vehicle.price }}</p>
</div>
</div>
<p class="dateAdded ml-auto ">{{ vehicle.date_added }}</p>
</div>
</div>
</template>
<script>
export default {
name: "SingleCar",
props: ["vehicle"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
}),
methods:{
working(){
console.log('working');
console.log(this.vehicle.make)
}
}
};
</script>
Thanks for your help.
So a few things you can try to fix this
in your Home.vue you can change
#emitData="testingEmit()"
to
#emitData="testingEmit"
// or
#emitData="testingEmit($event)"
You are telling to the function testingEmit that is not params to parse. So you need to take out the () and Vue will parse everything that comes from the $event or you cant say put the $event as a param in your testingEmit (second option).
For your SingleGarage.vue you can take the $parent.$emit and replace it with
this.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)
I have a Vue routes setup with the following parent and child routes:
{
path: '/dashboard', component: dashboard,
children: [
{
path:'report', component: report
}
]
},
And in the parent component I have some card menus to navigate to the child component and also a router-view:
<template>
<div class="container mt-5">
<div class="row mt-0 h-100">
<!-- menu item-->
<div class="col-lg-4 col-md-4 col-sm-4 col-xs-12 my-auto" id="report-card">
<router-link to="/dashboard/report" class="card2">
<div class="d-flex flex-column justify-content-center">
<div class="card-icon p-3">
<img :src="icons[0]"/>
</div>
<h3>Make a Report</h3>
<p class="small">Make a report of a crime or suspicious activities</p>
</div>
</router-link>
</div>
</div>
<router-view></router-view>
</div>
</template>
When I click on the router link, it does render the child component, but in the parent view.
Is there a way in which I could replace the parent view entirely with the child route instead of rendering or appending to the parent?
I am not sure if this is the right way to do it but you can use v-if with this.$route.name to check router name and render what you want to render.
For example your router will be like:
{
path: '/dashboard',
name: "Dashboard",
component: dashboard,
children: [
{
path:'report',
name: "Report",
component: report
}
]
},
and you can simply check if you are on parent route or not like this:
<template>
<div>
<div v-if="this.$route.name === 'Dashboard' ">
<!-- your homepage component -->
</div>
<router-view></router-view>
</div>
</template>