how to bind 2 classes at the same time? - vue.js

I need to make background color blue if element is current (bg-blue-400), green background (bg-green-400) - completed, dark (bg-dark-400) - uncompleted (only one can be activated). Example what I want:
My code:
<div v-bind:class="{'bg-blue-400' : isSelected(task), 'bg-grey-400' : isNotCompleted(task)}"
class="icon-block s30 text-white" >{{index+1}}</div>
Result:

Put this kind of logic within your script tag, and not within your template.
Based on the type of behaviour you want you need to choice between putting your logic within a computed property or method. (ask yourself the question: do you want the css class to change when your task changes? if so use computed property otherwise use a method)
Another improvement (since you are using a v-for) is to put your individual tasks in component.
Tasks component:
<task v-for="task in tasks" :task="task"></task>
Task component:
<template>
<div v-bind:class="cssClass">
<p> {{ task.name }} </p>
</div>
</template>
<script>
computed: {
cssClass: function () {
return {
'bg-blue-400' if this.task.selected
}
}
}
</script>

Related

How to refer to specific dynamically added component from number of dynamically added components in Vue, version 2.9

What or where is individual id or any other element that I can call to run a function on component.
Example: using add button I'm dynamically creating colored squares. Each square has close button.
Then I want to delete one of squares, let's say the blue one (third child of template).
v-bind:id='var' doesn't works because then all squares have the same id.
You can use ref, you can define ref in every added component. The ref is array. you can refrence component through index of the ref.
for example;<div v-for="(item) in arr" :key="item.id"> <span ref="testRef"> item.name </span> </div>, you can use 'this.$refs.testRef[index]' find component which you want.
There are multiple options. For instance, in your v-for loop you can get the index this way: (official docs)
<div v-for="(item, index) in myList" :key="index">
{{ item.name }}
</div>
You could then for example use the index in a ref to access the element for the script.
Another option is to pass the event instance to the onclick listener with the special keyword $event, and use this to get the element:
<div ... #click="myFunction($event)">
{{ item.name }}
</div>
And then in your script
myFunction(ev) {
// do something with the element that was clicked on
console.log(ev.target)
}
However, a more 'vue' way would be to not mess with the elements, but only manipulate the underlying data and let vue worry about representing the data with elements on your page. For instance, you could make your squares be represented by a list of objects, containing their own data own the state (opened or closed). So in your script you have:
data() {
return {
squares: [
{ color: "ff0000", opened: true },
...
]
}
},
And in your template something like
<div
v-for="square in squares"
v-show="suare.opened"
#click="square.opened = false"
> ...

How to have a scoped toggle variable in VueJS

I'm coming from an AngularJS background where the ng-repeat has a scoped variables and I'm trying to figure out how to achieve a similar result without the need to create a new component which seems overkill for a lot of situations.
For example:
<div class="item" v-for="item in items">
<div class="title">{{item.title}}</div>
<a #click="showMore = !showMore">Show more</a>
<div class="more" v-if="showMore">
More stuff here
</div>
</div>
In AngularJS that code would work great, but in VueJS if you click on show more it causes the variable to update for every item in the items list, is there anyway to create a local scoped variable inside of the v-for without the need to make a new component?
I was able to get it to work by having the showMore variable be something like #click="showMore = item.id" and then v-if="showMore.id = item.id" but that also seems like too much extra complexity for something that should be much simpler? The other problem with that approach is you can only get one item to show more rather than allow for multiple items to be toggled shown at once.
I also tried changing the items model to include item.showMore but once again that adds more complexity and it causes a problem if you need to update an individual item since the model is changed.
Are there any simpler approaches to this?
What do you think about this: CODEPEN
<template>
<div>
<h1>Items</h1>
<div v-for="item in items"
:key="item.id"
class="item"
>
{{item.name}}
<button #click="show=item.id">
Show More
</button>
<div v-if="item.id == show">
{{item.desc}}
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{id:1, name:"One", desc: "Details of One"},
{id:2, name:"Two", desc: "Details of Two"},
{id:3, name:"Three", desc: "Details of Three"}
],
show: null
};
}
};
</script>
<style>
.item{
padding: 5px;
}
</style>

vue.js "TypeError: Cannot read property 'path' of undefined"

I know this is a common question, but I have been going through my files now so many times without being able to the locate the error.
I am getting this error when I try to route to my components in my navigation menu.
My app.vue file:
<template>
<div id="app">
<Navbar
:nav-links="navLinks"
/>
<router-view/>
</div>
</template>
<script>
import Navbar from '#/components/Navbar'
export default {
components: {
Navbar
},
data: () => ({
navLinks: [
{
text: 'Home',
path: '/home'
},
{
text: 'About',
path: '/about'
},
{
text: 'Contact',
path: '/contact'
}
]
})
}
</script>
My Navbar component (This is where the error happens)
<template>
<nav>
<ul>
<li v-for="{link, index} in navLinks" :key="index"
#mouseover="hover = true"
#mouseleave="hover = false">
<router-link :to="link.path">
{{ link.text }}
</router-link>
</li>
</ul>
</nav>
</template>
<script>
export default {
props: ['navLinks'],
data(){
return {
hover: false,
}
}
}
</script>
How do I fix this?
<li v-for="{link, index} in navLinks" :key="index"...
should be
<li v-for="(link, index) in navLinks" :key="index"...
As it's now (destructured), link refers to a link property inside the object, not the object itself. Additionally, index is probably undefined, since the navLinks objects probably don't have an explicit property index. Therefore Vue might also complain about using invalid indexes in v-for.
Since you're only using the path prop, you could actually use destructuring, like this:
<li v-for="({ path }, index) in navLinks" :key="index"
#mouseover="hover = true"
#mouseleave="hover = false">
<router-link :to="path">
</li>
Another, unrelated note: hover property is currently being shared across all navLinks. If you expect it to somehow be related to the currently hovered element, yo uhave to save that separately (probably inside the navLink itself).
As for :nav-links="navLinks", what you've done is not only perfectly legal, but the recommended way of doing it (it's according to the HTML spec). Using :navLinks="navLinks" relies on Vue's HTML parser, which converts it to nav-links behind the scenes - inspect the HTML element and you'll notice it).
If you want to get into the details, you could have a look at this discussion on the subject. The result was: use either, but if you use camelCase it will be inconsistent with the rendered markup. If you use kebab-case, it will be consistent with rendered markup, so you won't have to deal with this difference when writing tests, for example, should you ever need to select elements by their attributes (jest converts camelCase to lowercase - hence it's inconsistent with the rendered markup, so the tests start passing/failing based on whether mount or shallowMount is used. Goes without saying, that's not really a good testing setup. )
The same exact discussion goes for using <SomeComponent /> vs <some-component />. While both work, using first needs to be addressed when writing tests if you need to select stubbed subcomponents.
Besides, vue/attribute-hyphenation (the way you did it) is part of the following vue linting presets:
plugin:vue/strongly-recommended
plugin:vue/vue3-recommended
plugin:vue/recommended
A prop in the Navbar component is named navLinks but you access it outside as nav-links.
This should work:
:navLinks="navLinks"
Incorrect syntax for v-for with {}. Use ():
li v-for="(link, index) in navLinks
You have done two mistakes here.
one is:
<template>
<div id="app">
<Navbar
:nav-links="navLinks"
/>
<router-view/>
</div>
Here you are binding with different name(nav-links), you should keep same name with which you are binding data and the name inside the props(navLinks).
Both names should be same.
Second one:
v-for="{link, index} in navLinks"
The syntax is wrong, the correct syntax should be
v-for="(link, index) in navLinks"

Conditional wrapper rendering in vue

I'm making a link/button component which either can have a button or an anchor wrapper, a text and an optional icon. My template code below is currently rendering either an anchor or a button (with the exact same content) based on an if statement on the wrapper element, resulting in duplicate code.
<template>
<a v-if="link" v-bind:href="url" class="btn" :class="modifier" :id="id" role="button" :disabled="disabled">
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</a>
<button v-else type="button" class="btn" :class="modifier" :id="id" :disabled="disabled">
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</button>
</template>
Is there a more clean way for wrapping my buttonText and icon inside either an anchor or button?
I've solved my issue by intensive Google-ing! Found this issue regarding Vue on Github which pointed me in the right direction.
Small piece of backstory
I'm using Vue in combination with Storybook to build a component library in which a button can either be a button or an anchor. All buttons look alike (apart from color) and can be used for submitting or linking. To keep my folder structure ordered, I would like a solution that generates a multiple buttons types (with or without link) from one single file.
Solution
Using computed properties I'm able to "calculate" the necessary tag, based on the url property of my component. When a url is passed, I know that my button has to link to another page. If there is no url property it should submit something or preform a custom click handler (not in the sample code below).
I've created the returnComponentTag computed property to avoid placing any complex or bulky logic (like my original solution) in my template. This returns either an a or a button tag based on the existence of the url property.
Next, as suggested by ajobi, using the :is attribute I'm able to define the component tag based on the result of my computed property. Below a stripped sample of my final (and working) solution:
<template>
<component :is="returnComponentTag" v-bind:href="url ? url : ''" class="btn" :class="modifier" :id="id">
{{buttonText}}
</component>
</template>
<script>
export default {
name: "Button",
props: {
id: {
type: Number
},
buttonText: {
type: String,
required: true,
default: "Button"
},
modifier: {
type: String,
default: "btn-cta-01"
},
url: {
type: String,
default: ""
}
},
computed: {
returnComponentTag() {
return this.url ? "a" : "button"
}
}
};
</script>
You could extract the wrapping element into a dedicated component.
<template>
<a v-if="link" v-bind:href="url" class="btn" :class="modifier" :id="id" role="button" :disabled="disabled">
<slot></slot>
</a>
<button v-else type="button" class="btn" :class="modifier" :id="id" :disabled="disabled">
<slot></slot>
</button>
</template>
// You would use it like this
<SomeComponent /* your props here */ >
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</SomeComponent>
There are multiple ways of doing this. Two examples would be the following based on the point of view:
You are defining two different components (Button or Anchor) and want to use a wrapper to render either one of them.
You could seperate the Wrapper Content into two components so that the wrapper only decides on which of the components to render (either the Button or the Anchor).
The problem with this approach could be you will have doubled code for methods and styling for the button and anchor component.
You are defining the content as a component and use the wrapper to define what to wrap the content in.
See Answer of https://stackoverflow.com/a/60052780/11930769
It would be great to know, why you would want to achive this. Maybe there are better solutions for your usecase. Cheers!

Draggable vue - how to access "column" id?

Right now I trying to use draggable to build a kanban (with four columns). I started creating two columns to learn about vue-draggable, but I can't figure out how to access the column of task.
My first two columns:
<draggable v-model="myArray" class="simple-a"
:options="{group:'people'}" :move="onMove"
#start="changeDrag" #end="changeDrag"
#change="seeChange">
<div v-for="(element, aIndex) in myArray" :key="aIndex" class="element-a">
{{ element.name }}
</div>
</draggable>
<draggable v-model="myArray2" class="simple-a" style="background:red;"
:options="{group:'people'}" :move="onMove" #start="changeDrag"
#end="changeDrag" #change="seeChange(1)">
<div v-for="(element2, aIndex) in myArray2" :key="aIndex" class="element-a">
{{ element2.name }}
</div>
</draggable>
I am using seeChange function to get which element has changed:
seeChange({moved, added, removed}, col) {
if(added){
console.log(added);
console.log('column: '+col);
}
if(moved){
console.log(moved);
}
if(removed){
console.log(removed);
}
}
As you can see I'm trying to pass col attribute to this function (change event), but I can't, is always undefined.
How can I pass an attribute to a change event of vue draggable ?
Or how is the correct/better way to get the new column of a edited item?
The syntax you need to pass additional parameters to an event handler is
template
<draggable ... #change="seeChange($event, 1)">
...
</draggable>
component
methods: {
seeChange(event, col) {
console.log(event, col)
...
}
},
Ref: Passing event and argument to v-on in Vue.js