Ill simplify the example. Basically i have multiple widgets on one page and i tought it would be a good practice not to copy all the widgets into v-dialog but use the refrence and apenned them into the dialog and back to the grid when needed. The problem is when i append my html into dialog and I try to run this.$refs vue loses track of infinite-loading componente... this.$refs does not contain ref="infinitiveLoading". If some1 can explain and maybe reccommend better practice.. thx
<div>
<div id="item_containerTest" ref="item_containerTest">
<span>Hello world</span>
<infinite-loading
ref="infinitiveLoading"
v-show="items.length !== 0 || this.loading"
#infinite="infiniteHandler"
>
<div slot="no-more"></div>
</infinite-loading>
</div>
<v-dialog v-model="scheduleDialog" id="dialog" ref="dialog"> </v-dialog>
</div>
//ignore itemID and columnID, i need them so i can append item back to the grid after dialog closes
openFullScreenDialog(itemId, columnId, title){
itemContainer = document.getElementById(`item_container${title}`);
dialog = document.getElementById("dialog");
dialog.append(itemContainer);
}
As Lucero said in the comments, you shouldn't manipulate the DOM with the classic javascript API. The breaks the shadow DOM of Vuejs (i.e. a runtime copy of the current DOM state in memory) and thus, its reactivity and components references.
If you have a content that have to be wrapped in different container depending on a prop for example, you can use slots for this :)
<template>
<div>
<div v-if="dialog">
<slot>
</div>
<v-dialog v-else v-model="openDialog">
<slot>
</v-dialog>
</div>
</template>
<script lang="ts">
import { Vue, Component } from 'nuxt-property-decorator'
export default Vue.extends({
name: 'GridOrDialog',
props: {
dialog: { type: Boolean, default: false }
},
data() {
return {
openDialog: true,
}
}
})
</script>
This way, you just have to declare the content once, and it will be on a v-dialog if you sets the prop dialog to true.
<GridOrDialog dialog="isDialogMode">
<span>Hello world</span>
<infinite-loading
ref="infinitiveLoading"
v-show="items.length !== 0 || this.loading"
#infinite="infiniteHandler"
>
<div slot="no-more"></div>
</infinite-loading>
</GridOrDialog>
Related
I'm trying to create a Tabs component in Vue 3 similar to this question here.
<tabs>
<tab title="one">content</tab>
<tab title="two" v-if="show">content</tab> <!-- this fails -->
<tab :title="t" v-for="t in ['three', 'four']">{{t}}</tab> <!-- also fails -->
<tab title="five">content</tab>
</tabs>
Unfortunately the proposed solution does not work when the Tabs inside are dynamic, i.e. if there is a v-if on the Tab or when the Tabs are rendered using a v-for loop - it fails.
I've created a Codesandbox for it here because it contains .vue files:
https://codesandbox.io/s/sleepy-mountain-wg0bi?file=%2Fsrc%2FApp.vue
I've tried using onBeforeUpdate like onBeforeMount, but that does not work either. Actually, it does insert new tabs, but the order of tabs is changed.
The biggest hurdle seems to be that there seems to be no way to get/set child data from parent in Vue 3. (like $children in Vue 2.x). Someone suggested to use this.$.subtree.children but then it was strongly advised against (and didn't help me anyway I tried).
Can anyone tell me how to make the Tab inside Tabs reactive and update on v-if, etc?
This looks like a problem with using the item index as the v-for loop's key.
The first issue is you've applied v-for's key on a child element when it should be on the parent (on the <li> in this case).
<li v-for="(tab, i) in tabs">
<a :key="i"> ❌
</a>
</li>
Also, if the v-for backing array can have its items rearranged (or middle items removed), don't use the item index as the key because the index wouldn't provide a consistently unique value. For instance, if item 2 of 3 were removed from the list, the third item would be shifted up into index 1, taking on the key that was previously used by the removed item. Since no keys in the list have changed, Vue reuses the existing virtual DOM nodes as an optimization, and no rerendering occurs.
A good key to select in your case is the tab's title value, as that is always unique per tab in your example. Here's your new Tab.vue with the index replaced with a title prop:
// Tab.vue
export default {
props: ["title"], 👈
setup(props) {
const isActive = ref(false)
const tabs = inject("TabsProvider")
watch(
() => tabs.selectedIndex,
() => {
isActive.value = props.title === tabs.selectedIndex
} 👆
)
onBeforeMount(() => {
isActive.value = props.title === tabs.selectedIndex
}) 👆
return { isActive }
},
}
Then, update your Tabs.vue template to use the tab's title instead of i:
<li class="nav-item" v-for="tab in tabs" :key="tab.props.title">
<a 👆
#click.prevent="selectedIndex = tab.props.title"
class="nav-link" 👆
:class="tab.props.title === selectedIndex && 'active'"
href="#" 👆
>
{{ tab.props.title }}
</a>
</li>
demo
This solution was posted by #anteriovieira in Vuejs forum and looks like the correct way to do it. The missing piece of puzzle was getCurrentInstance available during setup
The full working code can be found here:
https://codesandbox.io/s/vue-3-tabs-ob1it
I'm adding it here for reference of anyone coming here from Google looking for the same.
Since access to slots is available as $slots in the template (see Vue documentation), you could also do the following:
// Tabs component
<template>
<div v-if="$slots && $slots.default && $slots.default()[0]" class="tabs-container">
<button
v-for="(tab, index) in getTabs($slots.default()[0].children)"
:key="index"
:class="{ active: modelValue === index }"
#click="$emit('update:model-value', index)"
>
<span>
{{ tab.props.title }}
</span>
</button>
</div>
<slot></slot>
</template>
<script setup>
defineProps({ modelValue: Number })
defineEmits(['update:model-value'])
const getTabs = tabs => {
if (Array.isArray(tabs)) {
return tabs.filter(tab => tab.type.name === 'Tab')
} else {
return []
}
</script>
<style>
...
</style>
And the Tab component could be something like:
// Tab component
<template>
<div v-show="active">
<slot></slot>
</div>
</template>
<script>
export default { name: 'Tab' }
</script>
<script setup>
defineProps({
active: Boolean,
title: String
})
</script>
The implementation should look similar to the following (considering an array of objects, one for each section, with a title and a component):
...
<tabs v-model="active">
<tab
v-for="(section, index) in sections"
:key="index"
:title="section.title"
:active="index === active"
>
<component
:is="section.component"
></component>
</app-tab>
</app-tabs>
...
<script setup>
import { ref } from 'vue'
const active = ref(0)
</script>
I am trying to create a template with two parts
the tab (slot) -> could only get the slot to work with using a ref
The content (slot)
This component(tab) is wrapped in a component(tabs aka parent) that organizes the tabs based on certain props.
The overall goal is to create something like so:
https://getbootstrap.com/docs/4.0/components/navs/#tabs
Except with the ability to have custom tabs. For simplicity, I want to keep all the information relating to the tab within the tab component
1 - the header is not rendered in the component itself but pushed to the parent ***
2 - the tab component pushes the $ref to the parent and then the parent renders the HTML and listeners
How can i push(or another method to pass the information to the parent) data to the parent and keep all the listeners and js associated with the components in the tab slot
//tab component
<template>
<div>
<div class="tab" ref="tab">
<slot name="heading"> //-> Only available in setup context.slots if the default content is not used therefore resulted in using ref
//Default content
{{heading}} //-> if I add content to the heading slot via different components, the JS/listeners associated to those components do not work. I assume because I'm only pushing the HTML
</slot>
</div>
<div class="content ">
<slot/>
</div>
<div>
</template>
<script>
import {onMounted, ref} from '#nuxtjs/composition-api'
setup(props, {parent}){
const tab = ref()
onMounted(()=>{
let tab = {
data: tab.value //The entire ref
//data: tab.value.innerHTML => Works for passing the html but no listeners or js work
}
//parent has data.tabs = []
parent.data.tabs.push(tab)
})
return {
tab
}
},
props:{
....
}
</script>
//tab parent component (part to render tab via data.tabs)
<ul
>
<li
v-for="(child, index) in data.tabs"
class="s-tabs--li"
v-bind:key="index"
v-html="child.data"
></li>
</ul>
//Used in action
<s-tabs>
<s-tab heading="Home">
<div>
Home
</div>
</s-tab>
<s-tab heading="Service" icon="flower" tag="music">
<div>
Service
</div>
</s-tab>
</s-tabs>
I have a form fragment wrapped in a component that is hidden by v-if. When the user clicks a button, it toggles the boolean, revealing the hidden component, and when that happens I'd like to transfer focus to the first form input on the fragment.
I've tried using aria-live to no avail. I suspect the nature of the SPA interferes with the registration of those live regions (meaning my guess is that they must be registered when the page is rendered, as they don't seem responsive when injected into the DOM). I did not however, chase the answer down a rabbit hole so that is speculative. So then I added a class to the target input and tried to use HTMLElement.focus()
document.querySelector('.focus')[0].focus();
This also did not work. Does anyone know of a reason why I cannot seem to focus on an element that was recently injected into the DOM and is visible on the page at the time?
I think what's needed is for your form component to have something defined for when it's mounted:
Vue.config.productionTip = false;
const template = `<div>
<div>
<inner v-if="showInner" />
<button #click="toggle">Toggle inner component</button>
</div>
</div>`
const inner = {
name: 'inner',
template: '<form><input ref="textInput" type="text"/></form>',
mounted() {
this.$refs.textInput.focus()
}
};
new Vue({
template,
data: function() {
return {
showInner: false
};
},
methods: {
toggle() {
this.showInner = !this.showInner;
}
},
components: {
inner
}
}).$mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
I have this template dom-if using Polymer 1.X, and it is not working.
It is supposed to display 'Loading' when requirement.allLoaded is false and display the real content when requirement.allLoaded is true.
I switch the state of this variable in my test function. But it doesn't take effects.
//Properties
requirement: {
type:Object,
value: {
allLoaded: false,
tagsLoaded: false
}
}
//Test function
_testButton: function(){
console.log(this.requirement);
this.requirement.allLoaded = !this.requirement.allLoaded;
console.log(this.requirement);
},
<div id="modal-content">
<template is="dom-if" if={{!requirement.allLoaded}}>
<p>Loading</p>
</template>
<template is="dom-if" if={{requirement.allLoaded}}>
<iron-pages selected="[[selectedTab]]" attr-for-selected="name" role="main">
<details-tab name="details"></details-tab>
<bar-chart-tab name="barchart"></bar-chart-tab>
<live-data-tab name="livedata" shared-info='{{sharedInfo}}'></live-data-tab>
</iron-pages>
</template>
</div>
Note: I already used this structure to display/not display things in other project (with Polymer 2) and it was working.
Is it only the change that does not work? I.e. it shows correctly on load?
Try notifying Polymer of the change:
this.requirement.allLoaded = !this.requirement.allLoaded;
this.notifyPath('requirement.allLoaded');
You could also use this.set('property.subProperty', value) for Observable Changes.
In your case, that's this.set('requirement.allLoaded', !this.requirement.allLoaded);
I have following kind of code:
<div>
<compA />
<compB />
</div>
How do I make sure that first compA is rendered only after it compB is rendered.
Why I want is I have some dependency on few elements of compA, and style of compB depends on presence of those elements.
Why in details:
I have some complex UI design, where one box will become fixed when you scroll. SO It will not go above the screen when you scroll, it will be fixed once you start scrolling and it start touching the header. So I am using jquery-visible to find if a div with a particular id is visible on the screen, if it is not visible, I change the style and make that box fixed. Following code should give the idea what I am doing:
methods: {
onScroll () {
if ($('#divId').visible(false, false, 'vertical')) { // This is div from the compA, so I want to make sure it is rendered first and it is visible
this.isFixed = false
} else {
this.isFixed = true
}
}
},
mounted () {
window.addEventListener('scroll', this.onScroll() }
},
destroyed () {
window.removeEventListener('scroll', this.onScroll)
}
I dont want to make those in same component as one reason is it dont make sense as the nature of these components, and other I use compA at many places, while compB is specific to only one page. Also layout of these does not allow me to make compB child of compA as suggested in comments.
Any suggestions are welcome.
An option with events:
<!-- Parent -->
<div>
<comp-a #rendered="rendered = true"></comp-a>
<component :is="compB"></component>
</div>
<script>
// import ...
export default {
components: { CompA, CompB },
watch: {
rendered: function (val) {
if (val) this.compB = 'comp-b';
}
},
data() {
return {
rendered: false,
compB: null
}
}
}
</script>
<!-- Component B -->
<script>
export default {
mounted() {
this.$emit('rendered');
}
}
</script>
After going through the edit I realised that the dependency is not data driven but event driven (onscroll). I have tried something and looks like it works (the setTimeout in the code is for demonstration).
My implementation is slightly different from that of Jonatas.
<div id="app">
RenderSwitch: {{ renderSwitch }} // for demonstration
<template v-if='renderSwitch'>
<comp-a></comp-a>
</template>
<comp-b #rendered='renderSwitchSet'></comp-b>
</div>
When the component-B is rendered it emits an event, which just sets a data property in the parent of both component-A and component-B.
The surrounding <template> tags are there to reduce additional markup for a v-if.
The moment renderSwitch is set to true. component-a gets created.