How to add conditional styling in Vue.js using a method? - javascript

I want to add conditional styling in a child component based on the values of a prop passed from the parent component.
This a working example of conditional styling:
<li v-bind:class="[booleanValue ? 'stylingClassOne' : 'stylingClassTwo']"
but this is only applicable for when my styling is based on a single variable which can only be of two values (true/false).
I want to achieve conditional styling based on a variable that can take multiple values. Assume I pass a string from my parent component to my child component stylingDecider, which can be of values stylingClassOne, stylingClassTwo, stylingClassThree.
Therefore I want to do the following:
<li v-bind:class="getStylingClass(stylingDecider)"> but this does not work. The reason I need a method to decide what the styling is because there will be some other processing going on in the that will return a class based on said processing, so I can't just use <li v-bind:class="stylingDecider".
What am I doing wrong? Please advise, thanks.
I am using Vue 3 and bootstrap-vue 3.

I just created a working code snippet:
Vue.component('child', {
props: ['dynamicstyle'],
template: `<ul><li v-bind:class="getStylingClass(dynamicstyle)">Hello !!</li></ul>`,
methods: {
getStylingClass(stylingDecider) {
return stylingDecider;
}
}
});
var app = new Vue({
el: '#app',
data: {
stylingDecider: 'stylingClassTwo'
}
});
.stylingClassTwo {
background: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<child :dynamicstyle="stylingDecider">
</child>
</div>

Related

index in v-for for array v-if not updating - Vue.js

I've created a simple vue.js program that uses a for loop to toggle the visibility of text. I have a button that should toggle it, but when it is clicked, it changes the variable but doesn't update the button.
let app = new Vue({
data() {
return {
array: [true,true,false],
text: ["0","1","2"]
}
},
methods:{
change(index){
this.array[index]=!this.array[index]
console.log(this.array[index],index)
}
},
el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item,index) in text">
<button #click="change(index)">toggle show</button>
<a v-if="!array[index]">...</a>
<a v-else>{{item}}</a>
</div>
</div>
Is there any way I could get this simple app to work without changing the arrays?
You’re running into a reactivity caveat: you cannot update a specific entry in an array by index in that way.
You’ll need to use Vue.set or this.$set (the latter is simply an alias for the former):
change(index){
this.$set(this.array, index, !this.array[index]);
}

How to detect whether an element inside a component is overflown in Vue?

I have a component ResultPill with a tooltip (implemented via vuikit) for the main container. The tooltip text is calculated by a getter function tooltip (I use vue-property-decorator) so the relevant bits are:
<template>
<div class="pill"
v-vk-tooltip="{ title: tooltip, duration: 0, cls: 'some-custom-class uk-active' }"
ref="container"
>
..some content goes here..
</div>
</template>
<script lang="ts">
#Component({ props: ... })
export default class ResultPill extends Vue {
...
get tooltip (): string { ..calcing tooltip here.. }
isContainerSqueezed (): boolean {
const container = this.$refs.container as HTMLElement | undefined;
if(!container) return false;
return container.scrollWidth != container.clientWidth;
}
...
</script>
<style lang="stylus" scoped>
.pill
white-space pre
overflow hidden
text-overflow ellipsis
...
</style>
Now I'm trying to add some content to the tooltip when the component is squeezed by the container's width and hence the overflow styles are applied. Using console, I can roughly check this using $0.scrollWidth == $0.clientWidth (where $0 is the selected element), but when I start tooltip implementation with
get tooltip (): string {
if(this.isContainerSqueezed())
return 'aha!'
I find that for many instances of my component this.$refs.container is undefined so isContainerSqueezed doesn't help really. Do I have to somehow set unique ref per component instance? Are there other problems with this approach? How can I check whether the element is overflown?
PS to check if the non-uniqueness of refs may affect the case, I've tried to add to the class a random id property:
containerId = 'ref' + Math.random();
and use it like this:
:ref="containerId"
>
....
const container = this.$refs[this.containerId] as HTMLElement | undefined;
but it didn't help: still tooltip isn't altered.
And even better, there's the $el property which I can use instead of refs, but that still doesn't help. Looks like the cause is this:
An important note about the ref registration timing: because the refs themselves are created as a result of the render function, you cannot access them on the initial render - they don’t exist yet! $refs is also non-reactive, therefore you should not attempt to use it in templates for data-binding.
(presumably the same is applicable to $el) So I have to somehow recalc tooltip on mount. This question looks like what I need, but the answer is not applicable for my case.
So, like I've mentioned in one of the edits, docs warn that $refs shouldn't be used for initial rendering since they are not defined at that time. So, I've made tooltip a property instead of a getter and calcuate it in mounted:
export default class ResultPill extends Vue {
...
tooltip = '';
calcTooltip () {
// specific logic here is not important, the important bit is this.isContainerSqueezed()
// works correctly at this point
this.tooltip = !this.isContainerSqueezed() ? this.mainTooltip :
this.label + (this.mainTooltip ? '\n\n' + this.mainTooltip : '');
}
get mainTooltip (): string { ..previously used calculation.. }
...
mounted () {
this.calcTooltip()
}
}

Execute function when clicking on DOM element in Vue.js

I want to execute a function when I'm clicking on elements in the dom with a specific class. It just doesn't work, but I'm also receiving any error. This is my
code snippet:
methods: {
initTab: function(){
document.querySelectorAll('.element').onclick = this.nextTab()
}
},
mounted: function () {
this.initTab()
}
I
I want to execute the function every time I click on the element. Would be very thankful if anybody could help me :)
There's very little need (if at all) for document.querySelectorAll() in a Vue app.
In this situation you can take advantage of delegation:
<div #click="onClick">
<!-- Clicks on any element inside this div will be handled -->
</div>
methods: {
onClick(e) {
if (e.target.classList.contains('element')) {
// Handle the click
}
}
}
Add #click="initTab($event)" to the document or template root, that allows you to track every click event on your template, that way you could put your logic to the elements which have only .element class name. If you're using it in a component you could do : <template> <div #click="initTab($event)"> ... </div> </template>
var app = new Vue({
el: '#app',
data() {
return {
}
},
methods: {
nextTab(){
console.log("You clicked on an element with class name =element")
},
initTab(event){
let targetClassNames=event.target.className.split(" ");
targetClassNames.filter(e=>{
if(e==="element"){
this.nextTab();
}
});
}
},
mounted() {
}
})
#app{
height:100px;
display:grid
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app" #click="initTab($event)">
<button class="element">1</button>
<button class="element btn">2</button>
<button class="btn">3</button>
<button class="element btn-primary">4</button>
<button class="btn elementory">5</button>
</div>
You're trying to use general javascript logic within vue. This is not often a good idea.
What I do in such cases is something like this:
<component-name #click="nextTab(tabName)"></component-name>
However, in a v-for loop you can also do something like this:
<ul v-for="tab in tabs">
<li #click="nextTab(tab)">{{tab}}</li>
</ul>
That way in methods you only need:
methods: {
nextTab: function(tab){
// whatever it is you want to do here
}
},
And you won't need mounted at all.
Conclusion: try to avoid repetition by creating components or elements (like li) that repeat - not by trying to add an event-listener to a class.

Vue v-bind:class doesn't work on default truthiness check

So I have a simple v-bind:class like this: <div v-bind:class="{ showBranding: brandingEnabled }">BRANDING</div>
In my data, brandingEnabled is true and can be changed to false. Changing it to false does no remove the class.
It works perfect if I do this: <div v-bind:class="{ showBranding: (brandingEnabled == 'true') }">BRANDING</div>
Could this be an issue with my booleans being treated as strings? I have tried setting them (in my Vue data) to true, rather than "true" but that doesn't change anything either.
I have also tried setting the data to type: Boolean via props but to no avail.
I would really rather have it working with the simple syntax if possible...
Any help would be appreciated!
If showBranding is a CSS class, you have to add single quote around your css className, like this:
<div :class={'showBranding': brandingIsEnabled}>
// content
</div>
Then your class has to be inside a style tag into your component like this:
<style scoped>
.showBranding {
// content
}
</style>
Check also if your brandingIsEnabled data is inside your script tag like this:
<script>
export default {
data() {
return {
brandingIsEnabled: true
}
}
}
</script>
This example uses the single component syntax.
new Vue({
el: '#br',
data: {
showBranding: 'brandingClass',
brandingEnabled:true
}
});
CSS Code
.brandingClass{
display:none
}
html code
<div id="br">
<div :class="{ showBranding:brandingEnabled }">BRANDING</div>
</div>

VueJs manipulate inline template and reinitialize it

this question is similar to VueJS re-compile HTML in an inline-template component and also to How to make Vue js directive working in an appended html element
Unfortunately the solution in that question can't be used anymore for the current VueJS implementation as $compile was removed.
My use case is the following:
I have to use third party code which manipulates the page and fires an event afterwards. Now after that event was fired I would like to let VueJS know that it should reinitialize the current DOM.
(The third party which is written in pure javascript allows an user to add new widgets to a page)
https://jsfiddle.net/5y8c0u2k/
HTML
<div id="app">
<my-input inline-template>
<div class="wrapper">
My inline template<br>
<input v-model="value">
<my-element inline-template :value="value">
<button v-text="value" #click="click"></button>
</my-element>
</div>
</my-input>
</div>
Javascript - VueJS 2.2
Vue.component('my-input', {
data() {
return {
value: 1000
};
}
});
Vue.component('my-element', {
props: {
value: String
},
methods: {
click() {
console.log('Clicked the button');
}
}
});
new Vue({
el: '#app',
});
// Pseudo code
setInterval(() => {
// Third party library adds html:
var newContent = document.createElement('div');
newContent.innerHTML = `<my-element inline-template :value="value">
<button v-text="value" #click="click"></button>
</my-element>`; document.querySelector('.wrapper').appendChild(newContent)
//
// How would I now reinialize the app or
// the wrapping component to use the click handler and value?
//
}, 5000)
After further investigation I reached out to the VueJs team and got the feedback that the following approach could be a valid solution:
/**
* Content change handler
*/
function handleContentChange() {
const inlineTemplates = document.querySelector('[inline-template]');
for (var inlineTemplate of inlineTemplates) {
processNewElement(inlineTemplate);
}
}
/**
* Tell vue to initialize a new element
*/
function processNewElement(element) {
const vue = getClosestVueInstance(element);
new Vue({
el: element,
data: vue.$data
});
}
/**
* Returns the __vue__ instance of the next element up the dom tree
*/
function getClosestVueInstance(element) {
if (element) {
return element.__vue__ || getClosestVueInstance(element.parentElement);
}
}
You can try it in the following fiddle
Generally when I hear questions like this, they seem to always be resolved by using some of Vue's more intimate and obscured inner beauty :)
I have used quite a few third party libs that 'insist on owning the data', which they use to modify the DOM - but if you can use these events, you can proxy the changes to a Vue owned object - or, if you can't have a vue-owned object, you can observe an independent data structure through computed properties.
window.someObjectINeedtoObserve = {...}
yourLib.on('someEvent', (data) => {
// affect someObjectINeedtoObserve...
})
new Vue ({
// ...
computed: {
myObject () {
// object now observed and bound and the dom will react to changes
return window.someObjectINeedtoObserve
}
}
})
If you could clarify the use case and libraries, we might be able to help more.

Categories

Resources