This is a regular problem when dealing with dynamic DOM layouts in which some writable DOM properties are derived from other readable DOM properties. This is especially tough to reason about in declarative virtual DOM idioms like Mithril because they're based on the premise that every view function should be self-complete snapshots of UI state — which in this case isn't possible.
You have 3 options: you can either break out of the virtual DOM idiom to achieve this functionality by directly manipulating the DOM outside of Mithril's view, or you can model your component to operate on a '2 pass draw', whereby each potential change to the header element results in 1 draw to update the header and a second draw to update the content accordingly. Alternatively, you might be able to get away with a pure CSS solution.
Because you only need to update one property, you're almost certainly better off going for the first option. By using the config
function, you can write custom functionality that executes after the view on every draw.
return m('.body', {
style: {
height: '312px'
},
config : function( el ){
el.lastChild.style.height = ( 312 - el.firstChild.offsetHeight ) + 'px'
}
}, [
m('.header', /* header contents */),
m('.content', /* some contents */)
])
The second option is more idiomatic in terms of virtual DOM philosophy because it avoids direct DOM manipulation and keeps all stateful data in a model read and applied by the view. This approach becomes more useful when you have a multitude of dynamic DOM-related properties, because you can inspect the entire view model as the view is rendered — but it's also a lot more complicated and inefficient, especially for your scenario:
controller : function(){
this.headerHeight = 0
},
view : function( ctrl ){
return m('.body', {
style: {
height: '312px'
}
}, [
m('.header', {
config : function( el ){
if( el.offsetHeight != ctrl.headerHeight ){
ctrl.headerHeight = el.offsetHeight
window.requestAnimationFrame( m.redraw )
}
}, /* header contents */),
m('.content', {
style : {
height : ( 312 - ctrl.headerHeight ) + 'px'
}
}, /* some contents */)
])
}
A third option — depending on which browsers you need to support — would be to use the CSS flexbox module.
return m('.body', {
style: {
height: '312px',
display: 'flex',
flexDirection: 'column'
}
}, [
m('.header', {
style : {
flexGrow: 1,
flexShrink: 0
}
}, /* header contents */),
m('.content', {
style : {
flexGrow: 0,
flexShrink: 1
}
}, /* some contents */)
])
This way, you can simply state that the container is a flexbox, that the header should grow to fit its content and never shrink, and that the contents should shrink but never grow.