How to utilize a callback in a conditional in VueJS - vue.js

I need to change the focus styling in vueJS i have a call that find a focused element but I simply cant use it straight up in conditional rendering so I used a callback but I cant get the callback to work in the in line styling.
<template>
<button
...
:style="this.isThisElementActive() ? {'border':'solid 2px blue'} : ''"
ref="shipToStoreZipCodeButton"
>
{{ locationName }}
</button>
</template>
export default {
...
methods:{
... isThisElementActive(){
console.log(document.activeElement,this.$refs.shipToStoreZipCodeButton)
return document.activeElement === this.$refs.shipToStoreZipCodeButton
},
}

You could achieve that by using the :focus pseudo class :
<template>
<button class="my-btn" >
{{ locationName }}
</button>
</template>
<style>
.my-btn:focus{
border: solid 2px blue
}
</style>

In the template, you shouldn't use this
<template>
<button
...
:style="isThisElementActive() ? {'border':'solid 2px blue'} : ''"
ref="shipToStoreZipCodeButton"
>
{{ locationName }}
</button>
</template>
As this may cause unexpected bugs in runtime.
Question referance.
Why sometimes 'this' works and sometimes doesn't in Vue template?
Also, it would be better to use computed instead, as isThisElementActive function doesn't take any parameter.

Related

Vuetify v-select not trigger select event on option click with custom template

I am using Vuetify beta version (v3.0.0-beta.6) because I have to use Vue 3 and Vuetify 2.x is not supported by it.
I want to create an i18n selector with the country flag icon.
To manage i18n I use the vue-i18n library.
As mentioned in vue-i18n it is very simple to create this selector (https://vue-i18n.intlify.dev/guide/essentials/scope.html#locale-changing).
So what I'm doing is using the Vuetify <v-select> component to add my customization.
The problem is that the component looks the way I want it, but the selection behavior breaks.
There my code:
<template>
<v-select class="language-select" v-model="$i18n.locale" :items="$i18n.availableLocales" #change="setLocale($event)"
hide-selected variant="plain">
<template v-slot:selection="{ item }">
<v-icon> {{ getFlag(item) }} </v-icon>
</template>
<template v-slot:item="{ item }">
<v-list-item :key="`locale-${item.value}`" :value="item.value" :prepend-icon="getFlag(item)"
:title="getItemCaption(item)">
</v-list-item>
</template>
</v-select>
</template>
<script setup lang="ts">
import { useLocaleStore } from "#/stores/locale"
import selectCaption from "#/i18n/language-select-caption.json"
function setLocale(event: Event) {
useLocaleStore().setLocale((<HTMLInputElement>event.target).value);
}
function getFlag(locale: any) {
const xx = locale.value === 'en' ? "gb" : locale.value;
return "fi fis rounded-icon fi-" + xx;
}
function getItemCaption(locale: any) {
return selectCaption[locale.value];
}
</script>
<style lang="scss">
.language-select {
max-width: 60px;
margin-top: 35px;
}
.rounded-icon {
border-radius: 50%;
font-size: 24px;
}
</style>
NOTE: I need to use <v-list-item> in the item slot becasuse if I remove it all items are showed on the same line, as unique option.
Any idea on what i'm doing wrong?
Try adding adding v-bind to your v-list-item
<template v-slot:item="data">
<v-list-item
v-bind="data.props"
:key="`locale-${data.item.value}`"
:value="data.item.value"
:prepend-icon="getFlag(data.item)"
:title="getItemCaption(data.item)">
</v-list-item>
</template>
(edited for vuetify 3, per Jerod's comment below)

Why does this Vue3 transition break data binding?

I have this issue I've been hitting for hours now; I can't understand why it doesn't work as expected.
I pasted an example code below. The issue is that when editing the name, {{name}} is not updated. However, if I remove either of the <transition> element or the v-if="show" condition, then data binding works as expected. Same if the {{name}} is placed outside the transition.
So it seems the transition blocks data binding? However I don't find anything about it in the docs or elsewere. I tested this code in a Vue2 playground, and it works as expected (data binding works). So the behavior seems to depend on Vue3.
Is there something I'm missing? Is it a bug in Vue3?
Thanks in advance for any input or idea.
<template>
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<div v-if="show">
<p>hello, {{name}}</p>
<input v-model="name" type="text" />
</div>
</transition>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
data() {
return {
name: "",
show: true,
}
}
});
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.8s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
It works just fine in plain JS...
So try to focus on the differences:
TypeScript (i cannot use it here on SO) - I really doubt its the cause but you can try
Scoped CSS - did you tried to remove scoped ? There are some issues with scoped CSS and <transition>. Check this issue in Vue-loader. My example is not build with Webpack so Vue-loader is not used but it's for sure used in your project...
const app = Vue.createApp({
data() {
return {
name: "",
show: true,
}
},
template: `
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<div v-if="show">
<p>hello, {{name}}</p>
<input v-model="name" type="text" />
</div>
</transition>
</div>
`
}).mount("#app");
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.8s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0/vue.global.js"></script>
<div id="app"></div>
I meet same question, you can try to set the initial value of 'show' to false and at the lifeCycle to modify 'show' for true.

Window is not defined in nuxt js

So, I wanted to bind style with the height of another element that I got from getHeight function, but I kept getting an error that said window is not defined.
Can someone please give me a solution?
Here is my source code:
<template>
<div class="container">
<p class="section-title">past event</p>
<div class="columns is-multiline">
<div
class="column is-one-third is-centered past-events"
v-for="(event, index) in events.slice(0, 2)"
:key="index"
>
<EventCard :event="event" />
</div>
<div class="column is-one-third is-centered">
<div class="link-box" :style="{ height: getHeight() }">
<nuxt-link to="/past-events">
<p style="color: #ffffff; cursor: pointer" class="see-all">
Lihat List Event Lainnya
</p>
</nuxt-link>
</div>
</div>
</div>
<a class="see-all-btn"> </a>
</div>
</template>
<script>
import EventCard from "~/components/EventCard.vue";
export default {
name: "PastEvents",
components: {
EventCard
},
props: ["events"],
data() {
return {};
},
mounted() {
this.getHeight();
},
methods: {
getHeight() {
const height = window.getComputedStyle(
document.querySelector(".past-events")
).height;
console.log(height);
return height + "px";
}
}
};
</script>
Nuxt uses server-sided rendering. This means that when your code is being executed on the server, it does not have something like window. After all, it is not a browser.
The easiest way around this is by wrapping anything that should not be pre-rendered to html, with something like vue-no-ssr. This particular library renders a dummy component on the server, then actually renders the component when it gets to the browser.
Yes window doesn't exist during the mounted lifecycle hook. I assume you're trying to place something based on its position?
In that case, you might be able to utilize CSS to do it for you. You can place elements using the View Height/View Width units. Combine that with CSS calc() and you might get the solution you need.
Example:
.element {
/* make element positon relative to the window */
position: fixed;
/* set position - note vw/vh are % of window */
/* this put the top of your element -200px from the bottom of your window */
top: calc(100vh - 200px);
}
If you're doing something more complex, using Javascript's element.getBoundingClientRect() will likely provide what you need. See this answer for more info.

Nesting a slot in a slot for vue

Update: Here's a simplified version of what I'm trying to achieve here (from the threaded conversation below):
Accept Component A - Accept Component B - Accept a condition - if
condition is true : wrap Component B with Component A [and render]- else only
render component B.
I'm interested in creating a component that renders a wrapper conditionally. I figured a theoretical approach like this would probably be best**:**
<template>
<div>
<slot v-if="wrapIf" name="wrapper">
<slot name="content"></slot>
</slot>
<slot v-else name="content"></slot>
</div>
</template>
<script>
export default {
props: {
wrapIf: Boolean,
}
}
</script>
Then when we implement, it would look something like this:
...
<wrapper-if :wrap-if="!!link">
<a :href="link" slot="wrapper"><slot></slot></a>
<template slot="content">
content
</template>
</wrapper-if>
The idea being that, in this case, if there is a link, then let's wrap the content with the wrapper slot (which can be any component/element). If there isn't, then let's just render the content without the wrapped link. Pretty simple logic, but it seems that I'm misunderstanding some basic vue functionality because this particular example does not work.
What is wrong with my code or is there some kind of native api that already achieves this or perhaps a dependency that does this sort of thing already?
The output should look like this:
wrapIf === true
<a href="some.link">
content
</a>
wrapIf === false
content
Just focus on the content itself, and let the component worry about whether or not to wrap the default or named content slot.
If you need the wrapper to be dynamic, a dynamic component should solve that. I've updated my solution accordingly. So if you need the wrapper to be a label element, just set the tag property to it, and so on and so forth.
const WrapperIf = Vue.extend({
template: `
<div>
<component :is="tag" v-if="wrapIf" class="wrapper">
<slot name="content"></slot>
</component>
<slot v-else name="content"></slot>
</div>
`,
props: ['wrapIf', 'tag']
});
new Vue({
el: '#app',
data() {
return {
link: 'https://stackoverflow.com/company',
tagList: ['p', 'label'],
tag: 'p',
wrap: true
}
},
components: {
WrapperIf
}
})
.wrapper {
display: block;
padding: 10px;
}
p.wrapper {
background-color: lightgray;
}
label.wrapper {
background-color: lavender;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<wrapper-if :wrap-if="wrap" :tag="tag">
<a :href="link" slot="content">
content
</a>
</wrapper-if>
<div>
Change wrapper type:
<select v-model="tag">
<option v-for="tag in tagList">{{tag}}</option>
</select>
</div>
<button #click="wrap = !wrap">Toggle wrapper</button>
</div>

How to customize style of VueJS 2.0 using stylus?

I am using v-text-field without vuetify.min.css just use stylus.
Here is my code.
<template>
<v-text-field type="text" name="password"></v-text-field>
</template>
<style lang="stylus" scoped="scoped">
.input-group_details {
XXX
}
</style>
I am trying to hide some divs in v-text-field.
But I got nothing changed.
That is not possible using scoped styles (That's the point of scoping)
What you could do is either passing down a prop which indicates that the divs are hidden or handle it globally.
passing down a prop:
const textField = {
template: `
<div>
<div>Always shown</div>
<div v-if="shown">
Conditionally shown
</div>
</div>
`,
props: { shown: Boolean }
};
Vue.component('v-text-field', textField);
new Vue({}).$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<b>shown = true:</b>
<v-text-field :shown="true"></v-text-field>
<br>
<b>shown = false:</b>
<v-text-field :shown="false"></v-text-field>
</div>
As per https://vue-loader.vuejs.org/en/features/scoped-css.html#notes
you need to use >>> operator for CSS
So this should work:
<style scoped>
>>> .input-group_details {
//your css
}
</style>
You can use lang="stylus" and it will work, but your IDE might throw some syntax errors.
I'm not sure what's correct stylus syntax for that.
Note that it was implemented in v12.2.0