How can I add Multi class binding in Vue2 - javascript

I faced one problem when I put multi-class binding in Vue2.
<div class="list-item clearfix" v-on:click="selectItem(trail)" :class="popupMode ? 'popup' : ''">
Here popupMode is props and I'd like to add one more class binding when I select the Item(click function:selectItem()).
For example, similar to selected.
This class is defined. How can I manage this problem?

Update HTML class bind like:
v-bind:class="{ popup: popupMode, selected: isSelected }"
where isSelected is a new prop like popupMode. On element click when selectItem method is called, set the bool isSelected prop to true and you will be able to see selected class after that.
JS:
data: {
popupMode: true,
isSelected: false
},
methods: {
selectItem() {
this.isSelected= true;
}
}
For more info check this: Binding HTML Classes

You can try this method:
{ active: isActive, 'text-danger': hasError }
Don't forget place isActive and hasError in data (in this case).
I hope this help's.

Related

How to implement communication between two arbitrary elements in Vue?

I'm currently building an app using the Vue framework and came across a strange issue that I was unable to find a great solution for so far:
What I'm trying to do is add a class to a parent container in case a specific element inside the container (input, select, textarea etc.) gets focus. Here's the example code:
<div class="form-group placeholder-label">
<label for="desc"><span>Description</span></label>
<div class="input">
<input id="desc" type="text" />
</div>
</div>
In Vanilla JS of course, this is easily done:
const parent = document.querySelector('.placeholder-label');
const input = parent.querySelector('input');
input.addEventListener('focus', (e) => {
parent.classList.add('active');
});
In the same way, you could loop through all .placeholder-label elements and add the event to their child inputs/selects etc. to add this basic functionality. There are two moving parts here:
You don't know the type of the parent element, just that it has .placeholder-label on it.
You don't know the type of the child element, just that it is some sort of HTML form element inside the parent element.
Can I build a Vue component that toggles a class on a given parent element based on focus/blur of a given child element? The best I could come up with is use slots for the child elements, but then I still need to build a component for each parent. Even when using mixins for the reused parts it's still quite a mess compared to the five lines of code I need to write in pure JS.
My template:
<template>
<div
class="form-group"
:class="{ 'active': active }"
>
<label :for="inputID"><span>{{ inputLabel }}</span></label>
<slot
name="input"
:focusFunc="makeActive"
:blurFunc="makeInactive"
:inputLabel="inputLabel"
:inputID="inputID"
/>
</div>
</template>
<script>
export default {
name: 'TestInput',
props: {
inputLabel: {
type: String,
default: '',
},
inputID: {
type: String,
required: true,
},
},
// data could be put into a mixin
data() {
return {
active: false,
};
},
// methods could be put into a mixin
methods: {
makeActive() {
this.active = true;
},
makeInactive() {
this.active = false;
},
},
};
</script>
Usage:
<test-input
:input-i-d="'input-2'"
:input-label="'Description'"
>
<template v-slot:input="scopeVars">
<!-- this is a bootstrap vue input component -->
<b-form-input
:id="scopeVars.inputID"
:state="false"
:placeholder="scopeVars.inputLabel"
#blur="scopeVars.blurFunc"
#focus="scopeVars.focusFunc"
/>
</template>
</test-input>
I guess I'm simply missing something or is this a problem that Vue just can't solve elegantly?
Edit: In case you're looking for an approach to bubble events, here you go. I don't think this works with slots however, which is necessary to solve my issue with components.
For those wondering here are two solutions. Seems like I did overthink the issue a bit with slots and everything. Initially I felt like building a component for a given element that receives a class based on a given child element's focus was a bit too much. Turns out it indeed is and you can easily solve this within the template or css.
CSS: Thanks to #Davide Castellini for bringing up the :focus-within pseudo-selector. I haven't heard of that one before. It works on newer browsers and has a polyfill available.
TEMPLATE I wrote a small custom directive that can be applied to the child element and handles everything.
Usage:
v-toggle-parent-class="{ selector: '.placeholder-label', className: 'active' }"
Directive:
directives: {
toggleParentClass: {
inserted(el, { value }) {
const parent = el.closest(value.selector);
if (parent !== null) {
el.addEventListener('focus', () => {
parent.classList.add(value.className);
});
el.addEventListener('blur', () => {
parent.classList.remove(value.className);
});
}
},
},
},
try using $emit
child:
<input v-on:keyup="emitToParent" />
-------
methods: {
emitToParent (event) {
this.$emit('childToParent', this.childMessage)
}
}
Parent:
<child v-on:childToParent="onChildClick">
--------
methods: {
// Triggered when `childToParent` event is emitted by the child.
onChildClick (value) {
this.fromChild = value
}
}
use this pattern to set a property that you use to change the class
hope this helps. let me know if I misunderstood or need to better explain!

How to pass a prop in to a child component on click - Vue2

Idea behind the code is to make a child component active, when i click on it in a parent vue.
Parent vue file:
<PackageItem
v-for="pack in packagesData"
:key="pack.id"
#click.native="selectPackageItem(pack.id, pack.humanLabel, pack.humanPrice, pack.index)"
>
Child component:
props: {
selected: Boolean
},
data () {
return {
selected: selected
}
},
How can i send selected prop on click to a child component? If i set it this way:
<PackageItem
:selected="true"
#click.native="selectPackageItem(pack.id, pack.humanLabel, pack.humanPrice, pack.index)"
>
It would make all items selected to true, i want to set it on click only.
You can do like:
<PackageItem
v-for="pack in packagesData"
:key="pack.id"
#click.native="
selectPackageItem(pack.id, pack.humanLabel, pack.humanPrice, pack.index)"
:selected="selected[pack.id]"
>
Where selectedPackageItem method will set the selected package:
selectPackageItem(packId, packLabel, packPrice, packIndex) {
this.$set('selected', packId, true)
Or, if you want to toggle between true and false on click:
this.$set('selected', packId, !this.selected[packId])
In the data option, you should just have:
selected: []
Also, in your child component the following code is not required:
/* REMOVE
data () {
return {
selected: selected
}
},
*/
When you want to use selected props inside script, you just need to use this.selected and inside the template just use selected.
You can control the selection from the parent component by defining an array to store selected ids and a function to check selection.
data(){
return {
...
selected: []
}
},
methods: {
isSelected(id){
return this.selected.indexOf(id) > -1;
}
}
Then in your click method, push the id of the clicked item.
// Stub for `selectPackageItem` method
if(this.isSelected(id)){
// Maybe unselect?
} else {
this.selected.push(id);
}
Now you template becomes:-
<PackageItem v-for="pack in packagesData" :selected="isSelected(pack.id)" :key="pack.id" #click.native="selectPackageItem(pack.id, pack.humanLabel, pack.humanPrice, pack.index)">
I ended up with a more elegant solution:
<PackageItem
v-for="pack in packagesData"
:key="pack.id"
:selected="selectedPackages(pack.index)"
#click.native="selectPackageItem(pack.id, pack.humanLabel, pack.humanPrice, pack.index)"
>
Then added a method to check selected packages in an array:
selectedPackages (index) {
let packages = this.packages
let selected = packages.some(p => p.index === index)
return selected
}
selectedPackages returns a boolean value. this.packages is an array with active packages (clicked), that contains a lot of information besides ID and index. With .some i am getting true or false value, if clicked item's index is exact the same with the one in array (this.packages).

VueJS: Dynamic Class Binding

Vue -V -- 3.0.5.
I have a component Cube.vue in which I'm trying to set a blue or green class to a child element dynamically.
I've created the component and have it imported into a specific page but I can't get the or to work correctly.
<template>
<div :class="$style.cubeInner">
<div class="cube" :class="{ 'cube--blue': isBlue ? 'cube--green': isGreen }">
<div v-for="side in cubeside" :class="side.class" :key="side.id"></div>
</div>
</figure>
</template>
And here is my export
export default {
data() {
return {
Cube: 'cube',
isBlue: Boolean,
isGreen: Boolean,
};
}
};
I import into another component and render it via <cube-hover></cube-hover>. My question is do I need to set a prop or a data() for isBlue to be true or false? I can't seem to target the child since the entire component is being imported.
Basically, I can't target that nested <div>, it just adds the class to the parent. And I want to add 'cube--blue' or 'cube--green' to specific pages.
Put the boolean into a data field, and then the condition check into a computed function.
...updated to add context
export default {
data: () => {
...
isBlue: Boolean,
isGreen: Boolean,
},
computed:
isBlue() {
if (is it blue?) return true;
return false;
},
isGreen() {
if (is it green?) return true;
return false;
}
}
<template>
...
<div class="cube" :class="{ isBlue ? 'cube--blue' : 'cube--green': isGreen }">
<!-- I think this is where you went wrong: "'cube--blue': isBlue ? 'cube--green': isGreen" see note -->
</template>
note
You have a "?" separating your classes which should either be a comma, or you are trying to do a ternary operation. Comma separation could possibly apply both at once and I suspect you don't want that. Or if you are trying to do conditional class assignment:
Fix your ternary syntax:
`condition ? value if true : value if false`
you are missing the
: value if false portion
What you probably want is:
`:class="isBlue ? 'cube--blue' : 'cube--green'"
Lastly
Now that I've written this out I sort of feel like I should recommend a different approach. Assuming that this cube is either green OR blue, but never both at the same time, you might want to combine the logic into a single step. Perhaps you want to use a conditional inside of a getColor function? This is particularly smart if you will ever have more than two colors. Then the fn just returns a color and you can interpolate that into your class name like:
<div :class="`cube--${color}`"></i>
I can't understand what do you mean by 'or'.
By looking at your data just type:
<div class="cube" :class="{ 'cube--blue': isBlue, 'cube--green': isGreen }">
Update:
Kraken meant to change you approach to:
<div class="cube" :class="`cube--${getColor}`">
and in your data just type:
data() {
return {
color: 'blue',
};
},
computed: {
getColor() {
return this.color;
},
},
With this approach you prepare yourself for maybe more colors in the future. By just updating this.color.
<li
v-for="item in items"
:key="item.id"
class="nav-item"
:class="{ dropdown: hasChildren(item.children) }"
>
methods: {
hasChildren(item) {
return item.length > 0 ? true : false;
},
}
I think this is the best way to solve this problem.
<div class="checkbox-wrapper">
<div :class="[isInsurancePictureRequired === 'yes' ? 'custom-checkbox-active' : '', 'custom-checkbox']">
<label class="pic-required-checkbox-label" for="yes">
<input type="radio" id="yes" name="picture-require" value="yes" #click="handleCheckBox" checked>
<span class="checkmark"></span>
Yes
</label>
</div>

VueJS Show element on child component one at a time

So I have several components <server-header>. It has the following HTML:
<span #click="$parent.changeOrder(column, $event)">
<slot></slot>
<i v-show="sortActive" class="order-arrow" :class="sort"></i>
</span>
These components are inserted in another component <server-list>. These will be the headers, that when clicked, will order some lists. My objective is to only show one arrow icon at a time.
E.g.: If I click on the first header, the arrow appears on that one. If I click on the second header, the arrow from the first header hides and the one on the second header appears.
This would be simple to do with jQuery for example, but I'm kind of lost on how to do this with VueJS.
Don't call parent functions directly. That is an anti pattern. Instead use 2-way data binding with sync and this is much easier. Rough example below:
// server-list.vue
data() {
return {
selected: undefined
}
}
<server-header v-for="(header, idx) in headers" :header="header" :selected.sync="selected" :idx="idx"></server-header
Then in the child, we drop #click="$parent.changeOrder(column, $event)" in favor of:
#click="$emit('update:selected', idx)"
Make sure server-header has this prop expected:
props: ['idx', 'header', 'selected']
Then make sure we compare the idx to our header index:
<i v-show="selected === index"
I assume you're rendering the <server-header> components with a v-for directive, looping a server_headers property in your <server-list> component data property.
The way i go with this is adding a prop to the <server-header> component like selected, then I add a selected property to each of the server_headers objects. Then i render the icon (<i>) with v-show if the correspondent selected property is true.
Like this:
server-list component
Vue.component('server-list', {
data: () => ({
server_headers: [{key: value, selected: true}, {key:value, selected:false}]
}),
template: `
<div>
<server-header v-for="header of server_headers" :v-key="somekey" :selected="header.selected"></server_header>
</div>
`
}
Vue.component('server-header', {
props: ['selected'],
template: `
<span #click="$parent.changeOrder(column, $event)">
<slot></slot>
<i v-show="selected" class="order-arrow" :class="sort"></i>
</span>
`
})
Then, if i click on the arrow I would unset all the selected properties, and set the one i clicked. I hope this helps!!!!

In Ember can I pass markup to a component?

I'm trying to create a dropdown type component that can have some markup for the title, and then upon hovering reveal more markup. Something like this:
{{#dropdown-menu}}
{{#dropdown-header}}
<span>My Custom Title markup</span>
{{/dropdown-header}}
{{#dropdown-body}}
list of menu items
{{/dropdown-body}}
{{/dropdown-menu}}
The body should only show while some property like isExpanded is true. But if the body is clicked, isExpanded would become false.
I can make a working component that accepts a title property (string), but I can't figure out how to make the title include custom markup.
You can put the yield in your component in an if block. see this jsbin
component:
App.TestShowComponent = Ember.Component.extend({
layoutName: "components/test-show",
expanded: false,
actions: {
toggle: function () {
this.set('expanded', !this.get('expanded'));
}
}
});
index template:
{{#test-show}}
inner stuff
{{/test-show}}
component template:
<button {{action 'toggle'}}>toggle</button>
{{#if expanded}}
{{yield}}
{{/if}}

Categories

Resources