I have a question regarding the Masonry layout update. How can I trigger it in the Vue.js (Version 3) lifecycle hook "updated()"?
I have a card grid view like in the following code (using Bootstrap 5 here), but struggle to update the Masonry layout when e.g. a card changes its size. I know that I can use layout() see documentation but I don't know how to call it in my example.
So my main problem is, that I don't know how to make my Masonry object accessible in the updated function when initialized in the mounted() function. How do I do that?
<template>
<div
class="row justify-content-left"
data-masonry='{"percentPosition": true }'
>
<div class="col-md-6 col-lg-4 col-xxl-3 mb-4 card-col">
<Card 1 />
</div>
<div class="col-md-6 col-lg-4 col-xxl-3 mb-4 card-col">
<Card 2 />
</div>
<div class="col-md-6 col-lg-4 col-xxl-3 mb-4 card-col">
<Card 3 />
</div>
<div class="col-md-6 col-lg-4 col-xxl-3 mb-4 card-col">
<Card 4 />
</div>
</div>
</template>
<script>
[imports ...]
export default {
name: "ComponentName",
components: {
[components ...],
},
mounted: function () {
// initialize masonry
this.$nextTick(function () {
var row = document.querySelector("[data-masonry]");
new Masonry(row, {
// options
percentPosition: true,
});
});
},
updated: function() {
//-->how to call the relayout of Masonry here?
}
};
</script>
To cache a reference the resulting Masonry instance, just assign it to a property (you don't need reactivity on this instance, so attaching a property is fine):
export default {
mounted() {
this._masonry = new Masonry(this.$refs.masonry, ⋯)
},
updated() {
this._masonry.layout()
}
}
However, you'll notice that the this._masonry.layout() call does not actually re-layout the masonry items (likely due to some limitation of the library). The only workaround I see is to recreate the Masonry instance, and then you no longer need to cache a reference to it:
export default {
mounted() {
this.$nextTick(() => this.layout())
},
updated() {
this.layout()
},
methods: {
layout() {
new Masonry(this.$refs.masonry, ⋯)
}
}
}
demo
Related
I have in Layout.vue to components one TheSidebar second TheHeader, there is a button in TheHeader to open the sidebar in TheSidebarcomponent.
I need to when I click the button in header open the sidebar:
My try:
in TheHeader:
methods: {
openSidebar() {
this.$root.$emit("open-sidebar");
},
},
in TheSidebar
data() {
return {
sidebarOpen: false,
};
},
mounted() {
this.$root.$on("open-sidebar", (this.sidebarOpen = true));
},
I'm using VUE 3 so I got this error in console: TypeError: this.$root.$on is not a function so How can communicate ?
you can use something like tiny emitter it works fine and doesn't care about parent child relationship
var emitter = require('tiny-emitter/instance');
emitter.on('open-sidebar', ({isOpen}) => {
//
});
emitter.emit('open-sidebar', {isOpen : true} );
You can only pass props to a direct child component, and
you can only emit an event to a direct parent. But
you can provide and eject from anywhere to anywhere
Per another answer, provide and eject may be your best bet in Vue 3, but I created a simple example of how to implement with props/events. Built with Vue 2 as I haven't worked with 3 yet, but should be usable in Vue 3 as well.
Parent.vue
<template>
<div class="parent">
<div class="row">
<div class="col-md-6">
<h4>Parent</h4>
<hr>
<child-one #show-child-two-event="handleShowChildTwoEvent" />
<hr>
<child-two v-if="showChildTwo" />
</div>
</div>
</div>
</template>
<script>
import ChildOne from './ChildOne.vue'
import ChildTwo from './ChildTwo.vue'
export default {
components: {
ChildOne,
ChildTwo
},
data() {
return {
showChildTwo: false
}
},
methods: {
handleShowChildTwoEvent() {
this.showChildTwo = true;
}
}
}
</script>
ChildOne.vue
<template>
<div class="child-one">
<h4>Child One</h4>
<button class="btn btn-secondary" #click="showChildTwo">Show Child Two</button>
</div>
</template>
<script>
export default {
methods: {
showChildTwo() {
this.$emit('show-child-two-event');
}
}
}
</script>
ChildTwo.vue
<template>
<div class="child-two">
<h4>Child Two</h4>
</div>
</template>
<template>
<div class="card-deck mb-3 text-center">
<div class="card mb-3 box-shadow">
<div class="card-header">
Numbers Checked
</div>
<div class="card-body card-info color-accent" v-model="numbers_checked" v-text="numbers_checked">
</div>
</div>
</div>
</template>
<script>
export default {
props:
[
'overviewAnalytics',
],
data() {
return {
numbers_checked: this.overviewAnalytics.numbers_checked
};
},
created() {
this.channelTemperatureReading.listen('TemperatureReadingUpdate', reading => {
axios.get('/home/get-overview-analytics').then(resp => {
this.numbers_checked = 12; //resp.data.numbers_checked + 100;
});
});
},
computed: {
channelTemperatureReading() {
return window.Echo.channel('temperature-reading');
},
},
};
</script>
I've tried everything but text is not updating. Confirmed from every aspect that data does change.
Changes from AXIOS are coming just ok. I even tried to put custom value but no avail.
I don't what is issue here.
v-model only works on input, textarea, and select elements
You appear to be misusing computed properties which rely on reactive dependencies to execute however yours is wrapping window.Echo.channel('temperature-reading') which Vue knows nothing about.
I suggest you remove the computed property and use something like this
created() {
const channel = window.Echo.channel('temperature-reading')
channel.listen('TemperatureReadingUpdate', reading => {
axios.get('/home/get-overview-analytics').then(({ data }) => {
// console.log('get-overview-analytics', data.numbers_checked)
this.numbers_checked = data.numbers_checked + 100
})
})
}
As others have mentioned, v-model is not appropriate here so you should also remove that.
Don't use v-model with div, it's for inputs.
<div v-text="numbers_checked"></div>
From the documentation on v-model:
Usage: Create a two-way binding on a form input element or a
component.
I have am building a Vue app that includes a QuillJS editor in a tab. I have a simple setTab(tabName) Vue method that shows/hides tabs with the v-if directive.
methods: {
setTab: function (tabName) {
this.view = tabName;
if(tabName === 'compose') {
var editor = new Quill('#editor', {
modules: { toolbar: '#toolbar' },
theme: 'snow'
});
}
}
}
My tab is basically like this:
<div id="composer" v-if="tabName === 'compose'">
<!-- toolbar container -->
<div id="toolbar">
<button class="ql-bold">Bold</button>
<button class="ql-italic">Italic</button>
</div>
<!-- editor container -->
<div id="editor">
<p>Hello World!</p>
</div>
</div>
Currently, I'm getting an error because the #editor element does not yet exist when I am calling new Quill(...). How do I delay that QuillJS initialization on the page so that it doesn't happen until after the #editor is already there?
Use mounted hook.
mounted: function () {
// Code that will run only after the
// entire view has been rendered
}
Use this.$nextTick() to defer a callback to be executed after the next DOM update cycle (e.g., after changing a data property that causes a render-update).
For example, you could do this:
methods: {
setTab: function (tabName) {
this.view = tabName;
if(tabName === 'compose') {
this.$nextTick(() => {
var editor = new Quill('#editor', {
modules: { toolbar: '#toolbar' },
theme: 'snow'
});
})
}
}
}
A clean way to do this is not to rely on selectors but make Quill editor a self-contained component:
<template>
<div class="quill-editor">
<!-- toolbar container -->
<div ref="toolbar">
<button class="ql-bold">Bold</button>
<button class="ql-italic">Italic</button>
</div>
<!-- editor container -->
<div ref="editor">
<p>Hello World!</p>
</div>
</div>
</template>
<script>
...
name: "QuillEditor",
mounted() {
this.quill = new Quill(this.$refs.editor, {
modules: { toolbar: this.$refs.toolbar },
theme: 'snow'
});
}
...
</script>
I want to find a prettier solution on that problematic :
A click on a main component button set to true the var showUserAddModal in the store
// App.vue
// ...
computed: {
...mapGetters('admin', [ 'showUserAddModal' ])
}
// ...
methods: {
...mapMutations('admin', [ 'setShowUserAddModal' ])
}
// ...
A modal component is displayed :
<div class="modal" :class="{ 'is-active': showUserAddModal }">
I want to give focus into an inpput text localized in a sub-sub-component
CalendarAdminUserAdd.vue > AutoCompletion.vue
// App.vue
// ...
<div class="modal" :class="{ 'is-active': showUserAddModal }">
<div class="modal-background"></div>
<calendar-admin-user-add team-id="9" ref="calendaradminuseradd" />
</div>
// ...
// CalendarAdminUserAdd.vue
<template>
<div class="modal-card">
// ...
<auto-completion width="400px"
max-height="400px"
#input="selectUser($event)"
ref="adsearchcomponent" />
// ...
</template>
// AutoCompletion.vue
<template>
<div class="dropdown is-active">
<div class="dropdown-trigger">
<input type="text" class="input" ref="searchinput"
placeholder="Nom Prénom" v-model="searchFilter"
#keyup="searchUser" />
</div>
// ...
</template>
// ...
The issue is, with the use of the store, that the setShowUserAddModal method is coming from the store (mapMutations) and have, by nature, an asynchrone behaviour. And I can't figure out how I can catch the effective display event of the modal, to execute my focus() on it. For the moment, with a poor setTimeout, it works :
// App.vue
// ...
watch: {
showUserAddModal : function () {
setTimeout( () => {
this.$refs.calendaradminuseradd.$refs.adsearchcomponent.$refs.searchinput.focus()
}, 500)
}
}
// ...
Even with a nextTick() it doesn't work.
I use the framework CSS Bulma for the modal, with no Javascript, so I don't think that Bulma could interfer.
The store :
// store/admin.js
export default {
namespaced: true,
state: {
showUserAddModal: false
},
getters: {
showUserAddModal: state => state.showUserAddModal
},
mutations: {
setShowUserAddModal(state, showUserAddModal) {
state.showUserAddModal = showUserAddModal
}
}
}
Here I am, thank you for reading til here ;) and thanks for your help !
I'm just starting out with VueJS and I was trying to port over a simple jQuery read more plugin I had.
I've got everything working except I don't know how to get access to the contents of the slot. What I would like to do is move some elements passed into the slot to right above the div.readmore__wrapper.
Can this be done simply in the template, or am I going to have to do it some other way?
Here's my component so far...
<template>
<div class="readmore">
<!-- SOME ELEMENTS PASSED TO SLOT TO GO HERE! -->
<div class="readmore__wrapper" :class="{ 'active': open }">
<slot></slot>
</div>
Read {{ open ? lessLabel : moreLabel }}
</div>
</template>
<script>
export default {
name: 'read-more',
data() {
return {
open: false,
moreLabel: 'more',
lessLabel: 'less'
};
},
methods: {
toggle() {
this.open = !this.open;
}
},
}
</script>
You can certainly do what you describe. Manipulating the DOM in a component is typically done in the mounted hook. If you expect the content of the slot to be updated at some point, you might need to do the same thing in the updated hook, although in playing with it, simply having some interpolated content change didn't require it.
new Vue({
el: '#app',
components: {
readMore: {
template: '#read-more-template',
data() {
return {
open: false,
moreLabel: 'more',
lessLabel: 'less'
};
},
methods: {
toggle() {
this.open = !this.open;
}
},
mounted() {
const readmoreEl = this.$el.querySelector('.readmore__wrapper');
const firstEl = readmoreEl.querySelector('*');
this.$el.insertBefore(firstEl, readmoreEl);
}
}
}
});
.readmore__wrapper {
display: none;
}
.readmore__wrapper.active {
display: block;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id='app'>
Hi there.
<read-more>
<div>First div inside</div>
<div>Another div of content</div>
</read-more>
</div>
<template id="read-more-template">
<div class="readmore">
<!-- SOME ELEMENTS PASSED TO SLOT TO GO HERE! -->
<div class="readmore__wrapper" :class="{ 'active': open }">
<slot></slot>
</div>
Read {{ open ? lessLabel : moreLabel }}
</div>
</template>