Child component using parent component data to re-direct - vue.js

I currently have a website/app that displays "FeatureCard" (components) onto a "Grid" component.
The feature cards have all the basic props to pass to the parent (Grid) component, so that data can be grabbed and a v-for used to create as many "featureCards" as possible.
That is all fine and working.
The issue comes when I want a title of a Feature card to link to its on slug-route.
Here is a snippet of my "FeatureCard" component -
<div class="card-content">
<div class="row-1">
<!-- <h4 class="bold">{{ resourceTitle }}</h4> -->
<router-link :to="{ name: 'FigmaFind', params: {resource_slug: this.slug} }"><h4 class="bold">{{ resourceTitle }}</h4></router-link>
<button class="price"><h6 class="bold">{{ resourcePrice }}</h6></button>
</div>
<div class="row-2">
<a :href=creatorProfile target="_blank" >
<img :src="creatorImage">
</a>
<a :href=creatorProfile target="_blank" >
<p>By <strong>{{ creatorsName }}</strong></p>
</a>
</div>
<div class="row-3">
<a :href="downloadLink" class="btn main" target="_blank" >
<p class="light semibold" for="info" >Download Resource</p>
</a>
<a :href="resourceOriginalLink" class="btn icon" target="_blank">
<p class="material-icons light md-18">info</p>
</a>
</div>
</div>
As you can see, in "row-1" I have tried including a router-link, but obviously it doesn't work as it has not yet recieved the data for "this.slug" but essentially, that is where I want a user to click this, and be redirected to "website.com/selected-item-1"
My issue is, I do not pull the "slug" data in, until it is rendered onto the Grid component. So, until then I am not sure how to reference it for later use, I referenced the titles as "resource title" or links as ":href="resourceOriginalLink"" but no idea how to do it for a router item. I could just use ":href="resourceOriginalLink"" but I don't know how I would pass the route-info through...
any ideas?
UPDATE
So, I figured it out. It may be the wrong way to do it, or there may be an easier way. But here is what I have done.
I passed the data "slug" as a prop to my child component (featureCard).
I then set slug: this.resourceSlug in my data.
Then within my title section I simply set <router-link :to="{ name: 'FigmaFind', params: { resource_slug: this.slug }}"><h4 class="bold">{{ resourceTitle }}</h4></router-link>
And, it seems to be working!!
Obviously, if there is a better way please, someone let me know.

UPDATE
So, I figured it out. It may be the wrong way to do it, or there may be an easier way. But here is what I have done.
I passed the data "slug" as a prop to my child component (featureCard). I then set slug: this.resourceSlug in my data. Then within my title section I simply set {{ resourceTitle }}
And, it seems to be working!!
Obviously, if there is a better way please, someone let me know.

Related

Pass data from Vue loop to laravel component

I am having this issue and I can not find my way around it without duplicating lots of code.
I have an array of entries coming from an axios request. Everything will go in an ul.
If I am doing it like this, everything is ok:
resource-index.blade.php
<ul>
<li v-for="entry in entries" :key="entry.id" >
<div>
<div>
<a :href="entry.links.show">
<x-button-icon color="gray-400">
#include('partials.svg.outline-eye')
</x-button-icon>
<span class="ml-3">{{ __('View') }}</span>
</a>
</div>
<div>
<a :href="entry.links.edit">
<x-button-icon color="gray-400">
#include('partials.svg.pencil')
</x-button-icon>
<span class="ml-3">{{ __('Edit') }}</span>
</a>
</div>
</div>
</li>
</ul>
However, in case I want to try to extract some of that stuff in different components, the details I am sending from Vue no longer get passed to the component.
resource-index.blade.php
<ul>
<li v-for="entry in entries" :key="entry.id" >
<div>
<x-grid-list-item-button label="{{ __('View') }}" :href="entry.links.show">
<x-slot name="icon">
#include('partials.svg.outline-eye')
</x-slot>
</x-grid-list-item-button>
<x-grid-list-item-button label="{{ __('Edit') }}" :href="entry.links.edit">
<x-slot name="icon">
#include('partials.svg.pencil')
</x-slot>
</x-grid-list-item-button>
</div>
</li>
</ul>
And here is the grid-list-item-button.blade.php
<div>
<a href="{{ $href }}">
#if($icon)
<x-button-icon color="gray-400">
{{ $icon }}
</x-button-icon>
#endif
<span class="ml-3">{{ $label }}</span>
</a>
</div>
I already tried:
moving the href="entry.links.show" in a named slot;
passing the data with v-bind: v-bind:href="entry.links.show";
::href="entry.links.show"
Can someone please tell what am I doing wrong or at least point me in the right direction with this?
Thanks!
If I got you right: You are trying to pass data from Vue.Js to Laravel-Components. Unfortunately this is not possible. Blade gets processed on the server-side where Vue.Js is not yet available. So the variable entry.links.show do not yet exist in Laravel (only available on client-side) and therefore cannot be passed to the Laravel-Component. After the HTML got rendered by Blade and passed to the Browser, Vue.Js can pick it up and replicate your template for the v-for and generate your list. At this point your 'link'-variables get available.
Solutions:
You could extract your code to a Vue.Js-Component rather than a Laravel-Component. This would be interpreted on client-side.
An other solution would be to generate this list through Blade so you could use Laravel-Components.

v-on:click not working - dynamically added html element

I am trying to get all images for a category using Vue
<div class="col-md-12 col-sm-2 p-2">
<a v-on:click="onCategoryManageImageClick($event)" data-target="#location-
category-images">
</span>
</a>
</div>
So the event onCategoryManageImageClick ($event) does not work, if I am adding a html block and then click on get image button.
this is index.js
methods:{
onImagesTabClick(){
this.$root.$emit('activated-tab:location-images');
},
onCategoriesTabClick(){
window.j1App.eventBus.$emit("j1-location-image-list:shown");
},
onCategoryManageImageClick: function(event) {console.log('working event..');
event.preventDefault();
window.j1App.eventBus.$emit("j1-location-category-image-
list:shown",event.currentTarget.id);
}
}
So basically it need to to work like we do in jQuery
$(document).on('click',function{
})
So it works either page load or if adding any new html element on DOM. same I want in Vue.
You cannot alter the Vue template outside of Vue. That won't work. Vue compiles the template once when starting up and adds the event listeners to the rendered elements. If you add elements afterwards, Vue will not know about them.
The correct way of doing this would be to add those new elements in Vue.
<div
class="col-md-12 col-sm-2 p-2"
v-for="item in items"
:key="item.id"
>
<a
v-on:click="onCategoryManageImageClick($event, item)"
data-target="#location-category-images"
>
</a>
</div>
See https://v2.vuejs.org/v2/guide/list.html for documentation. In this case you need the items array variable in data and add more array elements to it, if you need more links.
Try raplacing your a tag with p
<div class="col-md-12 col-sm-2 p-2">
<p v-on:click="onCategoryManageImageClick" data-target="#location-
category-images">
</p>
</div>

using v-if to use different route-links in header cause route to wrong path

I am new to JavaScript Frameworks(although I used vanilla js or jquery(a little)) and I started with VueJS for my project.
I am writing a single page application that uses an restful-api as server.
I wanted that in header some links will appear if user is not logged-in(register, login) and disappear once logged in. And, of course, different set of links will appear.
a bit visualization :)
before login
HEADER => | Register(to='/register') Login(to='login')
after a successful login
HEADER => | Home(to='/') Profile(to='/profile') SomeOtherLink(to='/someOtherPath')
but sometimes, after login, although header is rendered correctly(I look in inspect and links were correct), It goes wrong pathes.
Such as first link before login was register and first link after login is home. When I clik home it goes to register. It is same for Profile and login. when I click profile it goes to /login. But third one works.
It seems somehow renders (not) wrong eventListener for links.
Here is my template.
...
<ul class="navbar-nav">
<template v-if="loggedIn">
<router-link :to="{name: 'Home'}"
tag="li"
class="nav-item"
active-class="active">
<a class="nav-link"> Home </a>
</router-link>
<router-link to="/profile"
tag="li"
class="nav-item"
active-class="active">
<a class="nav-link"> Profile </a>
</router-link>
<router-link :to="{name: 'SomeRoute'}"
tag="li"
class="nav-item"
active-class="active">
<a class="nav-link"> SomeLink </a>
</router-link>
</template>
<template v-else>
<router-link :to="{name: 'Register'}"
tag="li"
class="nav-item"
id="register-link"
active-class="active">
<a class="nav-link"> Kayıt Ol! </a>
</router-link>
<router-link
:to="{name: 'Login'}"
tag="li"
class="nav-item"
id="login-link"
active-class="active">
<a class="nav-link"> Giriş Yap </a>
</router-link>
</template>
</ul>
...
here loggedIn is computed property which is set true if user logs in, and false vice versa. It works correctly.
I tried using div instead of template tag but result was same.
How can I fix this?
Am I in wrong place? Or is it a bug?
I printed loggedIn and it was correct as I mentioned before.
Problem was not that though, Component rendered correctly. Problem was the click event listener I suppose.
Adding unique key to each route-link solved problem.
Most probably vue create an event-listener for each link, and once old links are destroyed and new links are rendered, it creates new listeners for new link but not remove old ones. Since there was no identity in links, it maps according to order.
Once old link is replaced with new one, it has two listener, one for old link that stayed in the same place, one for intended for the link. So it triggers one of them when clicked.
Actually it explains that why it works as intended first, after refresh works wrong, after another refresh again works correctly.
I'm not sure if it is a bug or not, but adding key is the only and true solution as it seems.

Getting Error "Elements in iteration expect to have 'v-bind:key' directives vue/require-v-for-key" in index.vue

I am new to vue.js. I have a simple index.vue which tries to connect to contentful and display the entries from contentful. My code in index.vue looks like this:
<template>
<div>
<!-- render data of the person -->
<h1>{{ person.fields.name }}</h1>
<!-- render blog posts -->
<ul>
<li v-for="post in posts">
{{ post.fields.title }}
</li>
</ul>
</div>
</template>
<script>
import {createClient} from '~/plugins/contentful.js'
const client = createClient()
However when I try localhost:3000 from my browser...it comes back with the following error:
ERROR in ./pages/index.vue
Module Error (from ./node_modules/eslint-loader/index.js):
C:\Users\admin\pdress\pages\index.vue
7:7 error Elements in iteration expect to have 'v-bind:key' directives vue/require-v-for-key
✖ 1 problem (1 error, 0 warnings)
I appreciate if someone can help me out to proceed further with my learning on vue.js please. Thanks in advance
What #ljubadr suggested is right. You need to do this:
<li v-for="post in posts" v-bind:key="post.id">
<!-- OR USE SHORTCUT NOTATION -->
<li v-for="post in posts" :key="post.id">
The reason has to do with performance. Attribute key helps Vue determine unique items in a list. Consider the example of sorting. If your UI has a sort button for posts, then your the order of items in post array will change. But does that mean Vue is going to re-render entire list? Of course not! Using :key it can determine if the item was already rendered on UI. It simply shuffles the DOM nodes and saves expensive rendering cycles.
Secondly, if you have complex components within your list when you are using v-for and :key is not provided, then whenever the list is changed or re-ordered, it simply changes the DOM but doesn't destroy existing components and that can cause local state mismatch. That is why it is must to have :key attribute.
Read Vue.js documentation for further information.
Note: Also remember that using v-for index for :key is a bad idea as it is not unique across your collection.
<template>
<div>
<!-- render data of the person -->
<h1>{{ person.fields.name }}</h1>
<!-- render blog posts -->
<ul>
<li v-for="post in posts" :key = "post">
{{ post.fields.title }}
</li>
</ul>
</div>
</template>
If this is not going to work use any unique field from your object for example if you have id in your object then,
:key = "post.id"
You must define a :key atribute for
v-for directive. And for <transition-group> tag.
for this case v-for you must explicit set,
<li v-for="(post, i) in posts" :key="i + 10">
{{ post.fields.title }}
</li>
If you noticed you can define max two argument in the v-for you must define the item (post) and can define the index of the post.

Vue - Nested Click Event Bubbling Issue

I have a menu button with a click event in Vue. When the button is clicked, it's supposed to activate the menu itself. This is the parent element which when clicked activates the menu (the toggleMenu function makes menuIsActive true). This part works fine:
<div class="navbar-item has-dropdown #click="toggleMenu">
<div class="navbar-link"></div>
<app-navmenu :class="{'is-active': menuIsActive}"/>
</div>
And this is the app-navmenu component that gets rendered:
<div class="navbar-dropdown" #click.stop>
<div class="container is-fluid">
<div class="column">
<h1 class="title">Title</h1>
<router-link class="navbar-item" :to="route" exact>
<div class="navbar-content">
<p class="has-text-info">info</p>
<small>meta info</small>
</div>
</router-link>
</div>
</div>
</div>
The problem I am running into is that I don't want the menu to disappear when I click on the actual navbar-dropdown div element, hence why I have a #click.stop. However, I do want the menu to disappear when I click on a router-link element, but since I have #click.stop in the navbar-dropdown element, the menu persists. If I don't have a #click.stop event on the navbar-dropdown element, then the menu disappears as soon as the navbar-dropdown element is clicked on, which I don't want.
How can I have the menu persist when clicking on the dropdown body, but also have it disappear when I click on a router-link? I've tried other click methods like .self and .prevent, but those don't seem to do what I need.
I am exactly not sure with your requirement, but following your comment, you can use something like this:
This will even push to the router link:
<router-link class="navbar-item" :to="route" exact
#click.native.prevent="callYourMethod">
This will prevent to go to the router link:
<router-link class="navbar-item" :to="route" exact
#click.native.prevent="callYourMethod" event="">
This is what I ended up doing to fix my issue. First, I moved the click function in the parent to one of its children:
<div class="navbar-item has-dropdown">
<div class="navbar-link" #click="toggleMenu"></div>
<app-navmenu :class="{'is-active': menuIsActive}"/>
</div>
This lets the body of the menu stay active even when I click on the body without having to use a #click.stop. Then in the menu itself, I did this so that links will close the menu:
<div class="navbar-dropdown" #click.stop>
<div class="container is-fluid">
<div class="column">
<h1 class="title">Title</h1>
<div #click="toggleMenu">
<router-link class="navbar-item" :to="route" exact>
<div class="navbar-content">
<p class="has-text-info">info</p>
<small>meta info</small>
</div>
</router-link>
</div>
</div>
</div>
</div>
One strange behavior I noticed is that if I put the #click="toggleMenu" function in the <router-link/> element itself, it doesn't get called on, even if I use .prevent. Hence the need for the div wrapper around the router-link element.