I attached an image to can exemplify the problem. I have 4 elements in page:
a table (which is an header with some data)
a chart
a table under the chart (which use chart labels as table header)
a footer
Because first table have only 2 rows (and is static), tableChart also is static (with only 5 rows) and the footer have only one row, I want to calculate dynamically the height of the chart.
So, I get window.height (or container) and decrease table height, tableChart height and footer height. Then, assign the value to chart element. I do this inside AfterViewInit lifecycle (I tried also DoCheck), but can't solve a problem: because #chart element is rendering in same time with the rest, when I get #chartTable height inside setChartHeight() method, it get only a part of total height necessary to render all 5 rows, so the chart will have a bigger height than the necessary.
To be more specific:
chartTable have an normal height of 100px;
because the setChartHeight() is called too early, chartTable is found having only 20px (I think can render only first row)
so, from the total of 500px (example) will decrease only 20px, instead of 100px and the chart height will be 480px (instead of 400px)
How can I wait to render all elements out of chart, and just at the final, to calculate the height of it in a right mode.
ngAfterViewInit(): void {
this.setChartHeight();
}
setChartHeight(): void {
let container = document.getElementById('container');
if (typeof container == 'undefined' || container == null) {
return;
}
let table = document.getElementById('table');
let chartTable = document.getElementById('chartTable');
let footer = document.getElementById('footer');
const height = container.clientHeight - table.clientHeight - chartTable.clientHeight -
footer.clientHeight;
let chart = document.getElementById('chart');
chart.style.height = height + 'px';
}
Another problem which I think exists is that chart and chartTable have same data source. So, when the source will came, because the chart element is above chartTable element, it will be rendered first. So, I need to render chartonly when I'm sure thatchartTable` is already displayed on the page.
edit: codesandbox example
thanks
Finally, how Strella and Liam said, I found a solution using css and avoiding reuse flex-layout library.
.container {
height: 100%;
display: flex;
flex-flow: column;
}
.table {
flex: 0 1 auto;
}
.chart-container {
flex: 1 1 auto;
display: flex;
flex-flow: column;
&__chart {
flex: 1 1 auto;
}
&__table {
flex: 0 1 100px;
}
}
Related
Suppose we need to set the autoheight of an ag-grid component it can be done easily with setting gridOptions to domLayout="autoHeight". This works in a single component, but for master-detail (parent/children) component that heights can be expanded, this doesn't work.
Same issue :
https://github.com/ag-grid/ag-grid/issues/205
I need to tweak deep into its css but still can't make it work,
Style reference: https://www.ag-grid.com/javascript-grid-styling/
Ag grid DOM Layout: https://www.ag-grid.com/javascript-grid-width-and-height/#gsc.tab=0
Example to reproduce :
https://github.com/ag-grid/ag-grid-vue-example (see on Master/Detail)
It's either tweaking the gridOptions getRowheight or its embedded css
for the related css :
.ag-root {
/* set to relative, so absolute popups appear relative to this */
position: relative;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
/* was getting some 'should be there' scrolls, this sorts it out */
overflow: hidden;
}
.ag-body {
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
https://github.com/ag-grid/ag-grid/blob/master/dist/styles/ag-grid.css
and a plunker inside:
https://www.ag-grid.com/javascript-grid-master-detail/#gsc.tab=0
Another clue from the author: https://www.ag-grid.com/javascript-grid-row-height/index.php#gsc.tab=0
Height for Pinned Rows Row height for pinned rows works exactly as per
normal rows with one difference - it is not possible to dynamically
change the height once set. However, this is easily got around by just
setting the pinned row data again which resets the row heights.
Setting the data again is not a problem for pinned rows as it doesn't
impact scroll position, filtering, sorting or group open/closed> positions as it would with normal rows if the data was reset.
You can dynamically calculate row height.
getRowHeight: function (params) {
if (params.node && params.node.detail) {
var offset = 80;
var allDetailRowHeight = params.data.callRecords.length * 28;
return allDetailRowHeight + offset;
} else {
// otherwise return fixed master row height
return 60;
}
}
You can find this example in official documentation of ag-grid.
For anyone finding this question in late 2021 and beyond, there's now the detailRowAutoHeight property in the gridOptions which will achieve the results desired by the asker.
https://www.ag-grid.com/javascript-data-grid/master-detail-height/#auto-height
const gridOptions = {
// dynamically set row height for all detail grids
detailRowAutoHeight: true,
// other grid options ...
}
Problem Statement
I have a grid, the element corners may or may not align but all the edges touch so there are no gaps. There are also no overlaps. For example, it's possible to have something like this:
+----+-------+
| | B |
| +---+---+
| A | C | |
| |---| D |
| | E | |
+----+---+---+
The grid is created via absolutely positioned elements. (I realize it may be easier to create such grid via a tree instead, where the parent node is the container forming a rectangle with neighboring element(s), but I think that may limit the ways in which I'd be able to resize elements - I'll explain later).
I want to be able to resize a single element and have neighboring elements recompute their dimensions such that they snap to the new element dimensions without leaving gaps. For example, let's assume we're resizing element C:
If I resize left edge of C towards A, I want A to shrink horizontally. Since A shrinks, both B and E have to expand towards A to fill that void.
If I resize bottom edge of C down, E should shrink, no other elements should be affected.
If I resize right edge of C into D, D should shrink, E and C should grow into that void.
If I resize top edge of C into B, B should shrink vertically and D should expand with C.
Why Tree Structure Won't Work
Now, as mentioned before, I realize that nesting these elements inside container elements (a tree-like structure) would handle the above case much easier. The reason I'm thinking a tree structure won't work for me (in addition to the fact that I already have too much code relying on absolute positions) is that I don't want the following case's resizing be dependent on the underlying tree structure that happens to be underneath:
+---+---+---+
| | | |
+---+---+---+
| | | |
+---+---+---+
| | | |
+---+---+---+
With a tree, this example wouldn't work, as the middle tile resizing would resize elements that happen to share the same parent/container, even if they don't need to resize.
Current Thoughts/Work
I'm trying to figure out how to compute which additional elements need to be resized in an efficient way for my absolute elements. I'm thinking of something along the following lines:
After resize that grows the element in a given direction, take the corresponding edge and perform document.elementsFromPoint() along this edge in a binary search pattern from one corner to another until the element returned for the min point is the same as that for the max point for every sampled point (if they're not the same, sample a new point at the midpoint and continue doing so recursively). This set of elements will contain all the elements that the element has invaded as a result of it's resizing (so they need to be shrunk by the opposite edge)
After a resize that shrinks the element, perform the same kind of binary edge traversal along the original edge (before the resize), but a couple pixels in the opposite direction from the resize (this should hit the elements that need to grow to fill the gap)
For the main element, it will be either one or the other bullet above (shrinking or growing), but the next step now is finding "side-effects", if the edge of the neighboring element goes beyond the edge of the original element, the same kind of analysis must be performed along this extension. This in turn may cause new side-effects along the same edge if we have a brick-like pattern.
The search explained in first bullet would be something like this, and then I would check for side-effects after:
function binarySearch(min, max, resizedElement, otherCoord, vertical=false) {
function getElement(x, y) {
if (vertical) {
let tmp = x;
x = y;
y = tmp;
}
// we know there will always be an element touching, so this
// should only throw an error if we pass bad otherCoord
return document.elementsFromPoint(x, y).filter(e => e !== resizedElement)[0];
}
let elements = new Set(),
startIndex = min,
startElement= getElement(min, otherCoord),
stopIndex = max,
stopElement = getElement(max, otherCoord);
if (startElement === stopElement) {
elements.add(startElement);
} else {
let middle = Math.floor((stopIndex + startIndex)/2),
left = binarySearch(min, middle, resizedElement, otherCoord, vertical),
right = binarySearch(middle, max, resizedElement, otherCoord, vertical);
elements = new Set([...elements, ...left, ...right]);
}
return elements;
}
Am I over-complicating this? Is there a better approach? Is this doable via trees and I'm just not seeing it?
If the underlying structure doesn't change then you can probably solve your problem with a tree structure css flexbox.
Flexbox is a very powerful layout tool that is native to modern browser engines. You use css to declare your layout using display: flex; among other simple css.
CSS trick's flexbox tutorial can explain it much better than I can so please refer to this to understand what's going on. The code below is more of a demo.
The idea is to alter the flexbox styles of the element. To resize the element, change the flex-basis using javascript. I just have buttons below to show the proof of concept but ultimately, you want to use mouse events to resize the elements. You can divide the event.clientX by the container width (container.clientWidth) to get a percentage of where the mouse is relative to the container and use that value for a flexbasis.
In the demo below, I'm using one variable to that I use to keep track of the flexbasis of the element .a and .a-complement. When you click the buttons, the flexbasis updates for each element. They both start off at 50% 50% and the grow/shrink by 10% which each button press. This example could be expanded to encompass resizing all the elements using the same technique. They would all respect each other's sizes and they would all have no gaps etc.
Moral of the story: let the layout engine do the work for you! Don't use absolute positioning unless you really have to.
To address the tree structure issues: you could restructure the tree moving divs into other divs when needed. If this complicates things too much then unfortunately the browser may not have native support for your document structure.
But it might in the future...
If flexbox doesn't solve your issue then the more experimental CSS GRID might, but note that CSS grid is only implemented in the lastest browser and no mobile browsers which might be okay given your target audience.
let aBasis = 0.5;
const elementA = document.querySelector('.a');
const aComplement = document.querySelector('.a-complement');
document.querySelector('#left').addEventListener('click', () => {
aBasis -= 0.1;
elementA.style.flexBasis = (aBasis * 100) + '%';
aComplement.style.flexBasis = ((1 - aBasis) * 100) + '%';
console.log((aBasis * 100) + '%', ((1 - aBasis) * 100) + '%');
});
document.querySelector('#right').addEventListener('click', () => {
aBasis += 0.1;
elementA.style.flexBasis = (aBasis * 100) + '%';
aComplement.style.flexBasis = ((1 - aBasis) * 100) + '%';
console.log((aBasis * 100) + '%', ((1 - aBasis) * 100) + '%');
});
.a {
display: flex;
background-color: red;
flex: 1;
justify-content: center;
align-items: center;
}
.b {
display: flex;
background-color: blue;
flex: 1;
justify-content: center;
align-items: center;
}
.c {
display: flex;
background-color: green;
flex: 1;
justify-content: center;
align-items: center;
}
.d {
display: flex;
background-color: yellow;
flex: 1;
justify-content: center;
align-items: center;
}
.e {
display: flex;
background-color: orange;
flex: 1;
justify-content: center;
align-items: center;
}
.h-container {
display: flex;
align-items: stretch;
flex: 1;
}
.v-container {
display: flex;
flex: 1;
flex-direction: column;
}
.container {
display: flex;
height: 200px;
width: 200px;
flex: 1
}
.example-container {
display: flex;
width: 100%;
}
<div class="example-container">
<div class="container">
<div class="h-container">
<div class="a">
<span>A</span>
</div>
<div class="a-complement v-container">
<div class="b">
<span>B</span>
</div>
<div class="h-container">
<div class="v-container">
<div class="c"><span>C</span></div>
<div class="e"><span>E</span></div>
</div>
<div class="d"><span>D</span></div>
</div>
</div>
</div>
</div>
<div>
<button id="left">move a to the left</button>
<button id="right">move a to the right</button>
</div>
</div>
I have 2 divs, a navigation and a main content in a bootstrap grid system. The length of either can vary depending on amount of content. I need them both styled to fill 100% of the browser window IF neither has the content to reach the bottom naturally. But if at least one of the divs has more content than the length of the browser window, I need to be able to scroll down the page with the styles of both divs remaining in tact and both have a height of the longer of the 2 divs.
I'm currently using a javascript resize function which seems to work but not in the case where neither div is long enough to fill the height of the browser window. Any suggestions?
HTML
<div class="row">
<div id="nav" class="col-xs-2">
Variable Height Navigation
</div>
<div id="main" class="col-xs-10">
Variable Height Content
</div>
</div>
Javascript
function resize() {
var h = (document.height !== undefined) ? document.height : document.body.offsetHeight;
document.getElementById("nav").style.height = h + "px";
}
resize();
window.onresize = function () {
resize();
};
I am trying to understand you question, and if I'm correct what you are looking for is:
Both divs need to be equally high
They need be at least the height of the screen
They need to take the height of the highest div
So let's try to achieve this goal as simply as possible:
var main = document.getElementById('main');
var nav = document.getElementById('nav');
function resize(){
var highest;
// Set the divs back to autosize, so we can measure their content height correctly.
main.style.height = 'auto';
nav.style.height = 'auto';
// Find the highest div and store its height.
highest = main.clientHeight > nav.clientHeight
? main.clientHeight
: nav.clientHeight;
// Check if the highest value is the div or the window.
highest = highest > window.innerHeight
? highest
: window.innerHeight;
// Assign the newly found value
main.style.height = highest + 'px';
nav.style.height = highest + 'px';
}
resize();
// Also, you don't need to wrap it in a function.
window.resize = resize;
// However, better would be:
window.addEventListener('resize', resize, false);
#main, #nav {
width: 100%;
height: auto;
}
#main { background: red; }
#nav { background: green; }
<div id="main"></div>
<div id="nav"></div>
Now, If you aren't bothered with the actual sameness in heiught of both divs but just want them to at least be one screenful, you should consider using CSS:
html, body { height: 100%; }
#nav, #main { min-height: 100%; }
I think that is the better solution (no Javascript!) and sort-of does what you want, bar the fact that you won't have to equally high div elements. However, you would barely notice it as each will at least fill the page.
You could try using viewport height:
For example:
#nav {
min-height: 100vh;
}
#main {
min-height: 100vh;
}
See Bootply.
This will also remove the need for JavaScript.
The title says it all really, but despite several tries I cant actually get this to work.
Im looking for a DIV to have a fixed height, and min-width, I will display text in the div (dynamically changing), should any text be long enough to reach the bottom of DIV I want the the width of the div to increase to accomodate all the text.
Is there a CSS solution ?
From what I've gathered, here is what I got
https://jsfiddle.net/Lu3tu98b/2/
div{
padding: 20px;
background-color: wheat;
}
.myDiv {
height: 100px;
min-width: 150px;
overflow: scroll;
}
Could you please provide your code.
OK, so its not possible to do this in CSS, so Javascript is the way to go.
For anyone else searching for a similar solution here is the javascript routine that I built to give me the functionality I required.
Basically it checks if the div is in overflow and if so increases the width by 5% increments until the overflow is resolved, if on load the div isnt in overflow it decreases the width by 5% until it finds the overflow point then steps back one notch.
function changedivsize(){
widthmin = 35; // in percent
widthmax = 80; // in percent
modified = false; // flag to signify when wheve modified the div larger
while ( $("#quotation").prop('scrollHeight') > $("#quotation").height() ) { // if div is in overflow
modified = true; // set flag so we dont start making div smaller in section below
var widthpixels = $('#quotation').width();
var parentWidth = $('#quotation').offsetParent().width();
var widthpercent = 100*widthpixels/parentWidth; // find current width % of div (3lines)
if (widthpercent < widthmax) { // if not at limit
widthpercent = Math.round(widthpercent+5); // add 5%
$("#quotation").width(widthpercent+"%"); // change div width
}else{
break; // if we are at limit call it a day
}
} // repeat adding 5% until either we are out of overload or at limit
while (modified == false){ // basically same as above but reduce div for smaller texts
var widthpixels = $('#quotation').width();
var parentWidth = $('#quotation').offsetParent().width();
var widthpercent = 100*widthpixels/parentWidth;
if (widthpercent > widthmin) {
widthpercent = Math.round(widthpercent-5);
$("#quotation").width(widthpercent+"%"); // change div width
if ( $("#quotation").prop('scrollHeight') > $("#quotation").height() ){
widthpercent = widthpercent+5;
$("#quotation").width(widthpercent+"%"); // change div width
break;
}
}else{
break;
}
}
};
Is this good for what you need?
http://jsfiddle.net/KefJ2/343/
#container {
position: relative; /* needed for absolutely positioning #a and #c */
padding: 0; /* will offset for width of #a and #c; and center #b */
border: green 3px dotted; /* just for the show */
float: left; /* To dynamicaly change width according to children */
height: 300px;
}
#a {
white-space: nowrap;
}
The source is here http://jsfiddle.net/4fV3k/
I have a function SetGridBorder which take style of border like 1px solid red and selector of wrapper like box-wrapper.
As my example code 4 rows is lived in a row so their is 4 cols and 4 rows. How I can determine it in JavaScript. I want to set the border in this rules.
the 2 and 3 in first row have missing left and right border (so this is not duplicate border).
2nd and third column (middles rows) have missing top and bottom border so no duplicate border for here also.
How I can do it in JavaScript? Do someone have suggestion for how to do it better?
$(document).ready(function () {
var box_wrapper = $(".box-box-wrapper", ".box");
SetGridBorder(4,4)
});
function SetGridBorder(style,selector) {
}
You can get how many rows and columns by divide wrapper width and height to box width and height. In your example wrapper height was zero, so i added overflow:auto; to body .box-wrapper class. Updated fiddle at http://jsfiddle.net/4fV3k/7/
function getRows() {
var wrapperWidth = $(".box-wrapper").width();
var boxWidth = $(".box-wrapper > div").width();
return Math.floor(wrapperWidth / boxWidth);
}
function getColumns() {
var wrapperHeight = $(".box-wrapper").height();
var boxHeight = $(".box-wrapper > div").height();
return Math.floor(wrapperHeight / boxHeight);
}
FYI.
body .box {
width: 128px;
height: 128px;
float: left;
text-align: center;
border:solid 1px #555;
}
$(document).ready(function () {
$.each($(".box"),function(i,n){
if((i+1)%4!=0){
$(n).css({'border-right':'none'})
}
if((i+1)>4){
$(n).css({'border-top':'none'})
}
});
});