Cannot execute scoped slot and function in the same event Vue - vue.js

I have the following code which uses this VueTailwind package:
<t-dropdown>
<div
slot="trigger"
slot-scope="{mousedownHandler, focusHandler, blurHandler, keydownHandler}"
>
<button
id="reseller-menu"
aria-label="User menu"
aria-haspopup="true"
#mousedown="mousedownHandler"
#focus="focusHandler"
#blur="blurHandler"
#keydown="keydownHandler"
>
{{ $page.props.auth.user.reseller.name }}
<icon icon="chevron-down" class-name="ml-2" />
</button>
</div>
<div slot-scope="{ blurHandler }">
<span v-for="user in users" :key="reseller.id" role="menuitem" class="block px-6 cursor-pointer py-2 hover:bg-indigo-500 hover:text-white"
#click="changeUser(user.id); blurHandler">{{ user.name }}</span>
</div>
</t-dropdown>
However, the blurHandler is not executed whenever the changeUser(user.id) method (a method in the parent component which seems to execute fine) is added to the #click event. The only way I was able to solve this issue was to use two different events such as the following:
<span v-for="user in users" :key="reseller.id" role="menuitem" class="block px-6 cursor-pointer py-2 hover:bg-indigo-500 hover:text-white"
#click="changeUser(user.id)" #mouseup="blurHandler">{{ user.name }}</span>
How can I use both in the same event since this doesn't seem to work in this case?

When the v-on value is a function reference, the template compiler transforms it into a function call, passing the event argument:
<button #click="functionRef">
<!-- ...is transformed into: -->
<button #click="functionRef($event)">
But when the v-on value is an expression, the template compiler wraps it in a function:
<button #click="onClick(); functionRef">
<!-- ...is transformed into: -->
<button #click="() => { onClick(); functionRef; }">
Notice functionRef; is not a function call, and effectively does nothing. To actually invoke the function in that expression, add the parentheses to make it a call:
<button #click="onClick(); functionRef()">
So your markup should be:
<div slot-scope="{ blurHandler }">
<span v-for="user in users" ⋯ 👇
#click="changeUser(user.id); blurHandler()">
{{ user.name }}
</span>
</div>
Also note slot-scope has been deprecated for v-slot, as of 2.6.0.

Related

How to get ID from conditionally rendered item in modal component?

I have a conditionally-rendered notes and I want to delete specific note, depends on ID.
I have no problem with deleting without modal, but my problem is that I want to delete note only when modal is accepted.
There is modal component:
<DeleteModal
:modalVisible="modalVisible"
:restore="modalType"
#closeModal="modalVisible = false"
#deleteItem="deleteNote(note.id)"
#restoreItem="restore()"
/>
Delete method:
const deleteNote = (id) => {
Inertia.delete(route('notes.destroy', id));
getNotes();
};
and there is how I render notes:
<div class="my-4" v-for="note in notesForIssue" :key="note">
<div class="flex flex-row justify-between"></div>
<div class="bg-gray-100 w-full min-h-16 mt-2 rounded whitespace-normal">
<div class="px-8 py-4">
<p>{{ note.text }}</p>
<div class="flex flex-row items-center justify-between">
<div class="flex flex-row text-xs">
<span class="text-[#2563EB]">{{ note.updated_at }}</span>
<p class="ml-2">{{ __('history.by') }}</p>
<p class="font-bold ml-2 text-[#2563EB]">{{ note.updated_by }}</p>
</div>
<div #click="modalType=false; modalVisible=true;">
</div>
</div>
</div>
</div>
</div>
But I have no access to note.id here: #deleteItem="deleteNote(note.id)". Any ideas, how to solve this problem?
Try this approach.
When you click on the div, instead of set visibality of modal,
set selectedItem
set visibility
then you accept by modal, you have a `selectedItem' for delete
After delete, clear the selectedItem.
First the key attribute is not accpet object type variable. Second maybe you should check what is notesForIssue first ,then find out why you can't access note.id

Nuxt.js/Vue.js dynamic images is not working

I'm getting some data from an API that contains an image and some other information. Now there's a problem in Nuxt js or maybe I'm wrong. Whenever I supply the path to the image in the :src it just doesn't work.
Here's my code:
<div v-for="(project, index) in projects" :key="project.p_id" class="swiper-slide">
<div :id="`index_${index}`" class="slide_wrapper">
<div class="background">
<img :src="getImgUrl(project.p_img_path)" alt="project cover image" />
</div>
<div class="details">
<div>
<div class="left">
<h2 style="color: black !impor tant">{{ project.p_label }}</h2>
<small>{{ project.p_date }}</small>
</div>
<div class="right">
<nuxt-link class="btn btn-secondary" to="/" #click="readMore">
<i class="fas fa-ellipsis-h"></i>
</nuxt-link>
</div>
</div>
</div>
</div>
</div>
script:
props: ['projects'],
methods: {
readMore() {},
getImgUrl(path) {
return '../../../' + path
},
},
Last thing, whenever I use required required('./assets/'+image.jpg') nuxt.js crushes in compilation always stops at 69%. I also tried required.context but it didn't work as well.
Any help will be appreciated. Thanks in advance.
In your template, change:
<img :src="getImgUrl(project.p_img_path)" alt="project cover image">
To:
<img :src="require(`../assets/${project.p_img_path}`)" alt="project cover image">
You shouldn’t need a method to return the path as a string, just use a template literal as above.
Ps. This assumes project.p_img_path = img/project_img/filename.jpg, so adjust as necessary.
The solution is that I had to put all the images inside the assets folder directly. Thank you all for your time.

How to update an ancestral element class in response to child input change with Vue.js?

I'm using Vue (v2.6.11) and I want to add/remove a class on an ancestor (grandparent) element of an input (checkbox) when it's value is changed. There are an unknown number of checkboxes so they are generated using v-for on the element that I want to add/remove the class. I have a function registered with my Vue instance methods when the checkbox value is changed.
In my code below, the <label> is the element to which I want to add/remove the class in response to the checkbox value changing. I'm trying to resist the urge to apply an :id attribute to the label and then do a lookup in my updateCounter method that will use the id of the checkbox to find the label and add/remove the class using pure JS. Is there a more Vue-esque way to do this?
<label class="item"
v-for="item in myItems"
v-bind:key="item.id"
:for="'item'+item.id">
<div>
<input type="checkbox"
:id="'item'+item.id"
v-model="item.isAccepted"
#change="updateCounter($event)" />
<span>{{ item.name }}</span>
<span>{{ item.status }}</span>
</div>
<div>{{ item.date }}</div>
</label>
just use a dynamic class like so: :class="item.applyClass ? 'item' : ' ' " so put a property on the item and then you can dynamically change that in your updatedCounter function
You can do
<label class="item"
v-for="item in myItems"
v-bind:key="item.id"
:for="'item'+item.id"
:class="{test: item.isAccepted === true}">
<div>
<input type="checkbox"
:id="'item'+item.id"
v-model="item.isAccepted"
#change="updateCounter($event)" />
<span>{{ item.name }}</span>
<span>{{ item.status }}</span>
</div>
<div>{{ item.date }}</div>
This would apply test class only when is Accepted is true for that particular checkbox.

Is it possible to concatenate class properties in vuejs?

General Question: I was wondering if I can concatenate class properties based on condition. See pseudo-code in the v-forline.
My use-case: I want to align all images that have an even index at the right side.
If I use flex flex-row-reverse for the parent section I get the images aligned on the right. But I don't know how to construct the class in such a way that I do not have to repeat the code for the child elements.
<section
v-for="(quote, index) of $frontmatter.quotes :class="lg:flex my-4 mx-12 overflow-auto" + {even: index % 2, odd: !(index % 2)}"
>
<img
class="quote-image right rounded-full h-64 w-64 flex items-center justify-center shadow-md mx-auto lg:ml-16 lg:mr-10 mt-12"
:src="$withBase(quote.image)"
/>
<div class="px-8 pt-6">
<h3 class="text-primary font-bold mb-4">
{{ quote.title }}
</h3>
<p>
{{ quote.text }}
</p>
</div>
</section>
And call the class extension something like:
.even {
flex-row-reverse
}
Currently,I use this structure - however, I am not happy with that, as I have to repeat my code for the child elements...
<section
v-for="(quote, index) of $frontmatter.quotes"
class= "my-16 mx-24 overflow-auto"
>
<div v-if="index % 2"
class="lg:flex flex-row-reverse">
<img
class="quote-image rounded-full h-64 w-64 flex items-center justify-center shadow-md mx-auto lg:ml-16 lg:mr-10 mt-12"
:src="$withBase(quote.image)"
/>
<div class="px-8 pt-6">
<blockquote>
<h2 class="text-primary font-bold mb-4">
{{ quote.title }}
</h2>
</blockquote>
<p class="quote-text">
{{ quote.text }}
</p>
</div>
</div>
<div v-else
class="lg:flex">
<img
class="quote-image rounded-full h-64 w-64 flex items-center justify-center shadow-md mx-auto lg:ml-16 lg:mr-10 mt-12"
:src="$withBase(quote.image)"
/>
<div class="px-8 pt-6">
<blockquote>
<h2 class="text-primary font-bold mb-4">
{{ quote.title }}
</h2>
</blockquote>
<p class="quote-text">
{{ quote.text }}
</p>
</div>
</div>
</section>
It should look something like:
First, just to clarify the question I'm trying to answer. Given the following code:
<div v-if="index % 2" class="lg:flex flex-row-reverse">
... children ...
</div>
<div v-else class="lg:flex">
... identical children ...
</div>
Is there a way to avoid the v-if and conditionally add the flex-row-reverse class instead?
You've got a number of options here. Firstly, the attributes class and style have special behaviour that allows you to specify both a bound and static copy of the same attribute. You can't do that for other attributes. e.g.
<div
class="lg:flex"
:class="{ 'flex-row-reverse': index % 2 }"
>
So the class lg:flex is added as a static class whereas, flex-row-reverse is added conditionally. Vue will combine them as appropriate to create the class attribute of the finished DOM nodes.
There are a number of other ways this could be written. Here are a couple to ponder:
<div :class="{ 'lg:flex': true, 'flex-row-reverse': index % 2 }">
<div :class="['lg:flex', { 'flex-row-reverse': index % 2 }]">
Arrays can be nested arbitrarily deep. Plain strings will be treated as classes to add. Objects will be treated as collections of conditional classes with the class name as the property name and the condition the property value.
All of this is using Vue's support for conditionally adding classes. However, a bound expression is just arbitrary JavaScript so you could apply the conditionality using normal JavaScript syntax instead of having Vue do it for you.
This is generally clunkier. This has to be a single expression so you can't use if/else. However, you can use ?: instead:
<div :class="'lg:flex' + (index % 2 ? ' flex-row-reverse' : '')">
The official documentation for these various ways to build classes is here:
https://v2.vuejs.org/v2/guide/class-and-style.html#Binding-HTML-Classes
Since you are using classes, not <b-img> props, perhaps the class you need is float-right instead of right
<img v-if="index % 2"
class="quote-image float-right rounded-full h-64 w-64 shadow-md mx-auto lg:ml-16 lg:mr-10 mt-12"
:src="$withBase(quote.image)"
/>

Can't remove item from dynamically generated list

I'm having trouble with a function that I've created using scotch.io's VueJS guide. It's supposed to remove a function from the list on click, (triggering a view update) but I keep getting this console error:
[Vue warn]: Property or method "$index" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
(found in root instance)
The guide doesn't say to declare anything in the data option, what am I missing?
Here's the HTML:
<a href="#" class="list-group-item" v-for="event in events">
<h4 class="list-group-item-heading">
<i class="glyphicon glyphicon-bullhorn"></i>
{{event.name}}
</h4>
<h5>
<i class="glyphicon glyphicon-calendar" v-if="event.date"></i>
{{event.date}}
</h5>
<p class="list-group-item-next" v-if="event.description">{{event.description}}</p>
<button class="btn btn-xs btn-danger" v-on:click="deleteEvent($index)">Delete Event</button>
</a>
And the relevant JS:
new Vue({
...
methods: {
...
deleteEvent: function(index) {
if (confirm("Are you sure you want to delete this event?")) {
this.events.$remove(index);
}
}
}
});
There's already been a couple of cases where I've had to swap out deprecated directives that the guide uses, (v-repeat, v-on="click", etc) is the problem something similar?
The guide you are following is slightly outdated.
In Vue 2.0, using the index requires a little change in the syntax:
<a href="#" class="list-group-item" v-for="(event, index) in events">
<!-- You have to ask for it: ^^^^^^^^^^^^^^ -->
<button class="btn btn-xs btn-danger" v-on:click="deleteEvent(index)">
<!-- Then use it the way you named it: ^^^^^ -->
Delete Event
</button>
</a>