I'm trying to calculate and set an element's max-height
style programmatically based on the number of children it has. I have to do this on four separate elements, each with a different number of children, so I can't just create a single puted property. I already have the logic to calculate the max-height
in the function, but I'm unable to pass an element from the template into a function.
I've tried the following solutions with no luck:
<div ref="div1" :style="{ maxHeight: getMaxHeight($refs.div1) }"></div>
This didn't work because$refs
is not yet defined at the time I'm passing it into the function.Trying to pass
this
or$event.target
togetMaxHeight()
. This didn't work either becausethis
doesn't refer to the current element, and there was no event since I'm not in av-on
event handler.
The only other solution I can think of is creating four puted properties that each call getMaxHeight()
with the $ref
, but if I can handle it from a single function called with different params, it would be easier to maintain. If possible, I would like to pass the element itself from the template. Does anyone know of a way to do this, or a more elegant approach to solving this problem?
I'm trying to calculate and set an element's max-height
style programmatically based on the number of children it has. I have to do this on four separate elements, each with a different number of children, so I can't just create a single puted property. I already have the logic to calculate the max-height
in the function, but I'm unable to pass an element from the template into a function.
I've tried the following solutions with no luck:
<div ref="div1" :style="{ maxHeight: getMaxHeight($refs.div1) }"></div>
This didn't work because$refs
is not yet defined at the time I'm passing it into the function.Trying to pass
this
or$event.target
togetMaxHeight()
. This didn't work either becausethis
doesn't refer to the current element, and there was no event since I'm not in av-on
event handler.
The only other solution I can think of is creating four puted properties that each call getMaxHeight()
with the $ref
, but if I can handle it from a single function called with different params, it would be easier to maintain. If possible, I would like to pass the element itself from the template. Does anyone know of a way to do this, or a more elegant approach to solving this problem?
- 1 Think about custom directive - you can simply access bound element within. vuejs/v2/guide/custom-directive.html – Daniel Commented Mar 27, 2019 at 14:28
3 Answers
Reset to default 2A cheap trick I learned with Vue is that if you require anything in the template that isnt loaded when the template is mounted is to just put a template with a v-if on it:
<template v-if="$refs">
<div ref="div1" :style="{ maxHeight: getMaxHeight($refs.div1) }"></div>
</template>
around it. This might look dirty at first, but the thing is, it does the job without loads of extra code and time spend and prevents the errors.
Also, a small improvement in code length on your expandable
-function:
const expandable = el => el.style.maxHeight =
( el.classList.contains('expanded') ?
el.children.map(c=>c.scrollHeight).reduce((h1,h2)=>h1+h2)
: 0 ) + 'px';
I ended up creating a directive like was suggested. It tries to expand/press when:
- It's clicked
- Its classes change
- The element or its children update
Vue ponent:
<button @click="toggleAccordion($event.currentTarget.nextElementSibling)"></button>
<div @click="toggleAccordion($event.currentTarget)" v-accordion-toggle>
<myComponent v-for="data in dataList" :data="data"></myComponent>
</div>
.....
private toggleAccordion(elem: HTMLElement): void {
elem.classList.toggle("expanded");
}
Directive:
Accordion.ts
const expandable = (el: HTMLElement) => el.style.maxHeight = (el.classList.contains("expanded") ?
[...el.children].map(c => c.scrollHeight).reduce((h1, h2) => h1 + h2) : "0") + "px";
Vue.directive("accordion-toggle", {
bind: (el: HTMLElement, binding: any, vnode: any) => {
el.onclick = ($event: any) => {
expandable($event.currentTarget) ; // When the element is clicked
};
// If the classes on the elem change, like another button adding .expanded class
const observer = new MutationObserver(() => expandable(el));
observer.observe(el, {
attributes: true,
attributeFilter: ["class"],
});
},
ponentUpdated: (el: HTMLElement) => {
expandable(el); // When the ponent (or its children) update
}
});
Making a custom directive that operates directly on the div element would probably be your best shot. You could create a directive ponent like:
export default {
name: 'maxheight',
bind(el) {
const numberOfChildren = el.children.length;
// rest of your max height logic here
el.style.maxHeight = '100px';
}
}
Then just make sure to import the directive in the file you plan on using it, and add it to your div element:
<div ref="div1" maxheight></div>