Dynamically flexible width & number items with flex - javascript

I have a set of tags, that I want to show in the client. However, sometimes you might have too many tags and you want to show only one row of tags maximized to your body's width without setting a fixed number of columns or item width, and adding a show more button at the end of the tag list with the same style as a tag.
I have achieved this using Javascript in my Angular project by doing the following:
Find out the width of your tags container dynamically, with ViewChild on my content container:
let contentWidth = this.contentContainer.nativeElement.clientWidth;
Calculate the text width of the see more button and use it to calculate the new content width minus see more button width:
Calculating text function does the following:
const canvas = document.createElement('canvas'); // create a canvas
const context = canvas.getContext('2d'); // get the context
context.font = '12px avertastd-bold'; // set up your font and size
And calculate the text width:
const seeMoreButtonWidth = context.measureText(seeMoreButtonText).width;
Create a new array variable 'previewTags' which will hold the tags that are visible when the tags body is in collapsed state, and fill in as many tags as you can by calculating each tag's width with it's content text you receive from the API by checking if the next tag + its padding (static value) fits into the width.
(Not runnable here)
for (const tag of this.data.tags) {
const width = context.measureText(tag).width;
if (contentWidth - (width + this.tagsPadding) > 0) {
previewTags.push({text: tag});
contentWidth -= (width + this.tagsPadding);
} else {
break;
}
}
Push the see more button at the end of previewTags list:
previewTags.push({text: seeMoreButtonText, isButton: true});
And it looks like this in the html:
<ng-container *ngFor="let tag of previewTags">
<div class="tag" [ngClass]="{'see-more-button': tag.isButton}">{{tag.text}}</div>
</ng-container>
Output:
Resize:
As you see, now the tags are flexiable (this code does not include the show more functionality).
After giving you this background and understanding of what I am doing, I would love to ask if this is possible to achieve with css or less JavaScript intervation?

Something like this could be a pure css solution if your tags have a constant height. I just let the flex-list wrap around and then don't show the overlap.
.content_wrapper {
display: flex;
justify-content: flex-start;
flex-direction: rows;
}
.tag_wrapper {
display: flex;
justify-content: flex-start;
flex-direction: rows;
flex-wrap: wrap;
width: 80%;
height: 32px;
overflow: hidden;
}
.tag_wrapper div {
width:100px;
height:30px;
border: 1px solid black;
}
button {
flex-grow: 4;
}
<div class="content_wrapper">
<div class=tag_wrapper>
<div>Tag1</div>
<div>Tag2</div>
<div>Tag3</div>
<div>Tag4</div>
<div>Tag5</div>
<div>Tag6</div>
<div>Tag7</div>
<div>Tag8</div>
<div>Tag9</div>
</div>
<button>See more</button>
You could probably make the "See more" button solution more elegant, to not have as much white space but I'll leave that to you :)

Here is some javascript to remove the see-more button if it's not needed.
(OBS) this only works if all the tags are the exact same width and have the same margin. I did this to avoid looping through all values and checking their width individually.
(I know the list is in the wrong order, I made it like that to get the see-more button fit in well without having to tinker a bunch.
function getWidthWithMargin(elem) {
var style = elem.currentStyle || window.getComputedStyle(elem)
margin = parseFloat(style.marginLeft) + parseFloat(style.marginRight)
return(elem.getBoundingClientRect().width + margin)
}
function handleWindowSizeChange() {
let tags = document.getElementsByClassName("tag");
if(tags.length != 0)
{
let tag_width = getWidthWithMargin(tags[0]);
if(tags[0].parentElement.getBoundingClientRect().width/tag_width > tags.length) {
document.getElementById("see-more-button").style.display = "none";
}
else{
document.getElementById("see-more-button").style.display = "block";
}
}
}
window.onload = handleWindowSizeChange;
window.onresize = handleWindowSizeChange;
.content_wrapper {
}
.tag_wrapper {
display: flex;
justify-content: flex-start;
flex-direction: row-reverse;
flex-wrap: wrap;
width: 100%;
height: 32px;
overflow: hidden;
}
.tag_wrapper div {
min-width:100px;
height:30px;
border: 1px solid black;
margin: 10px;
}
.tag_wrapper button {
height:30px;
flex-grow: 50;
}
<div class="content_wrapper">
<div class=tag_wrapper>
<button id="see-more-button">See more</button>
<div class="tag">Tag1</div>
<div class="tag">Tag2</div>
<div class="tag">Tag3</div>
<div class="tag">Tag4</div>
<div class="tag">Tag5</div>
<div class="tag">Tag6</div>
<div class="tag">Tag7</div>
<div class="tag">Tag8</div>
</div>

Related

Center an absolute element below a div?

I'm trying to center a pargraph below a div (could be a square image for the matter). I understand that the easiest way to do it is to contain both the div and the text below it in a single container and use text-align, but in this instance I have a limitation where I cannot let the small image be contained in a container wider than that image.
Without centering the text it looks like this:
My code:
HTML:
<div class="block"></div>
<p class="label">This text is a bit long</p>
CSS:
body {
padding: 5rem;
}
.block {
background-color: blue;
width: 80px;
height: 80px
}
.label {
}
Codepen:
https://codepen.io/omerh3/pen/oNqVjvV
The reason why I cannot let the image be in a container is that I'm using ReactFlow where the handles should connect to the sides of the image without a gap. If I put the image and the text inside a div, the div will take the width of the text and thus it will naturally be wider than the image.
I tried centering the text below the image with absolute positioning, but with different paragraphs sizes, it won't be persistent in the center. Is there a away to achieve this without inserting the image/square and the text inside one div?
One last thing: the width of the image if constant, for example 100px
one way to do this is to get the coordinates of your two elements and then add margin-left: to adjust the position of the span
let divOffsets = document.getElementById('a').getBoundingClientRect();
let divRight = divOffsets.right;
let divLeft = divOffsets.left;
console.log(divLeft,divRight)
let spanOffsets = document.getElementById('b').getBoundingClientRect();
let spanRight = spanOffsets.right;
let spanLeft = spanOffsets.left;
console.log(spanLeft,spanRight)
let divCenter = divLeft + divRight / 2
console.log(divCenter)
let offset = divCenter - (spanLeft + spanRight / 2)
offset = offset + "px"
document.getElementById('b').style.marginLeft = offset;
body {
padding: 5rem;
border:solid 1px red;
position:relative;
}
.block {
background-color: blue;
width: 80px;
height: 80px;
}
span{
position:absolute;
}
<div id = 'a'class="block"></div>
<span id = 'b' >1234</span>
Do you mean something like this??
body {
padding: 5rem;
}
.block {
background-color: blue;
width: 80px;
height: 80px;
margin:0 auto;
}
p.label {text-align: center}
<div class="block"></div>
<p class="label">This text is a bit long</p>

Move overflowed text to beginning when focusing out contentEditable element

Take a look at this example: https://jsfiddle.net/qpysmb9t/
Whenever text in the contentEditable element becomes bigger that the max-width of the div(try typing some long text), than the left part gets hidden and what's on the right is shown. This is okay while you type, but on focus out I'd like to reverse this and show text from the beginning.
<div tabindex="-1" contenteditable="true" class="name-data">This is test</div>
.name-data{
max-width:180px;
white-space: nowrap;
overflow-x: hidden;
}
The usual answer is to move caret position to the start, however that does not move the text along the way and it also messes with element not focusing out anymore. Check it out: https://jsfiddle.net/qpysmb9t/1/
What do you recommend that I do?
A dash of JavaScript helps. When the div loses focus we can use scrollLeft to get back to the begin position.
//scroll the text back to the beginning when focus is lost
document.querySelector("div.name-data[contenteditable='true']").addEventListener("blur", function(e){
this.scrollLeft = "0px";
}, true);
.name-data{
max-width:180px;
white-space: nowrap;
overflow-x: hidden;
border: 1px solid #949494;
}
<div tabindex="-1" contenteditable="true" class="name-data">This is test</div>
IDEA: Make your div display: flex and toggle justify-content propertive when user focus out to force browser re-paint the content
CSS only solution:
.project-name-data {
max-width: 180px;
white-space: nowrap;
overflow-x: hidden;
display: flex;
justify-content: flex-start;
align-items: center;
flex-wrap: wrap;
border: solid 1px gray;
}
.project-name-data:focus{
justify-content: flex-end;
}
<div tabindex="-1" contenteditable="true" #projectName class="project-name-data">This is test</div>
I like CSS only solution but it weird because the div content have different align from two state focus and normal. So i add a bit javscript to fix it
Javascript solution:
document.getElementsByClassName("project-name-data")[0].addEventListener("focusout", function() {
this.style.justifyContent = "flex-end";
// Wait a tick then change the justifyContent back to force browser re-paint
setTimeout(() => {
this.style.justifyContent = "flex-start";
}, 0)
});
.project-name-data {
max-width: 180px;
white-space: nowrap;
overflow-x: hidden;
display: flex;
justify-content: flex-start;
align-items: center;
flex-wrap: wrap;
border: solid 1px gray;
}
<div tabindex="-1" contenteditable="true" #projectName class="project-name-data">This is test</div>
For DIV contenteditable, we need to fix a scroll left bug. Combined both Duannx and Mouser answers into another example (my own project).
// Fix DIV contenteditable scroll left bug
var elements = document.querySelectorAll('div[contenteditable="true"]');
for (let i = 0; i < elements.length; i++)
{
elements[i].addEventListener('focus', setScrollLeft, true); // The handler is executed in the capturing phase
elements[i].addEventListener('blur', setScrollCenter, true); // The handler is executed in the capturing phase
}
function setScrollLeft()
{
this.scrollLeft = '0px';
this.style.justifyContent = 'flex-start';
}
function setScrollCenter()
{
this.scrollLeft = '50%';
this.style.justifyContent = 'center';
}

Let the highest element control the flex wrapper height

I'm designing a footer mega menu containing a number of submenus. The content is dynamic. I want to make the largest submenu span a whole column of the wrapper, and therefore also set the height of it.
I've come up with a solution where I'm measuring the height of the blocks and sets the wrapper height to the height of the highest block.
While this works OK - The solution feels a bit naive and something tells me there are more elegant solutions to it. Preferably without javascript. What do you think? I tried with CSS grid but could not get the result I wished for.
nav {
margin: 0 auto;
width: 80%;
display: flex;
flex-direction: column;
align-content: center;
flex-wrap: wrap;
}
The height of the wrapper element is set with Javascript
/*
Set the height of the wrapper to the highest blocks height
*/
function rearrangeBlocks() {
// Grab the whole menu
const section = document.querySelector('nav');
// Grab all the blocks
const allBlocks = document.querySelectorAll('.block');
// Grab the highest block
const longestBlock = Array.from(allBlocks).reduce((longest, current) => {
if (current.getBoundingClientRect().height > longest.getBoundingClientRect().height) {
return current;
} else {
return longest;
}
});
// Set the height of the menu to the same height of the highest menu item
section.style.height = `${longestBlock.offsetHeight}px`;
}
Codepen
Change some CSS
nav {
margin: 0 auto;
width: 80%;
display: flex;
flex-direction: row;
background: red;
}
https://codepen.io/anon/pen/RemZmb

How to break line automatically after slash?

I have a div on the left and a canvas on the right, both filling the whole screen. Depending on the item I point to in the canvas, I show some information on the div (element.innerHTML = '...'). The problem is, some texts are too long and get hidden to the right side of the div (I don't want to use a scrollbar).
Usually the long text is composed of slash-separated names, like name1/name2/name3. If the slash separated the text to a new line, my problem would be solved, but it doesn't. Some possible solutions would be:
1) substitute '/' with '/ ', but it gets ugly if the text fits in a single line. For the same reason, I can't add \n to the html. Also, this text is meant to be copied, so even adding some styling to hide the space is not what I need.
2) substitute '/' with another separator character that automatically breaks the line (are hyphens my only friend here? They don't look appropriate for my case).
3) use overflow-wrap: break-word, but it will break the word in the middle, and I prefer it to be broken right after the slash.
4) automatically increase the div width without messing with the canvas position and size (body is using flex-direction:row). That would be the best solution, I think, because it would also solve the rarest cases where the problem is not with the slash.
I made a jsfiddle to illustrate, you can see that some slashes break the text, while others don't (and the text breaks before the slash, which I think is ugly. Anyway, if I have to accept the text being broken before the slash, it still needs to break before ALL the necessary slashes!)
HTML
<body>
<div id='data'>
<button onclick='c()'>
Get Text
</button><br>
<br>
Default text.<br>
<br>
<span id='text'></span>
</div>
<canvas id='canv'>
</canvas>
</body>
CSS
html {
height: 100%;
}
body {
height: 100%;
margin: 0;
padding: 0;
display: flex;
flex-direction: row;
overflow: hidden;
}
#data {
padding:10px;
background-color: #CCF;
overflow-y:auto;
overflow-x:hidden;
}
#canv {
background-color: #CFC;
}
JavaScript
var data = document.getElementById('data');
var canv = document.getElementById('canv');
canv.width = window.innerWidth - data.offsetWidth;
canv.height = window.innerHeight;
function c() {
var text = document.getElementById('text');
text.innerHTML = 'longname1/longname2/longname3/longname4';
}
You can wrap the slashes using span and apply some styling to make them close to text:
var data = document.getElementById('data');
var canv = document.getElementById('canv');
canv.width = window.innerWidth - data.offsetWidth;
canv.height = window.innerHeight;
function c() {
var text = document.getElementById('text');
text.innerHTML = 'longname1<span>/</span> longnam<span>/</span> longname3<span>/</span> longname4<span>/</span> lllllllll<span>/</span> lon<span>/</span> aa';
}
html {
height: 100%;
}
body {
height: 100%;
margin: 0;
padding: 0;
display: flex;
flex-direction: row;
overflow: hidden;
}
#data {
padding: 10px;
background-color: #CCF;
overflow-y: auto;
overflow-x: hidden;
}
#canv {
background-color: #CFC;
}
#text span {
letter-spacing: -4px;
}
<body>
<div id='data'>
<button onclick='c()'>
Get Text
</button>
default text
<br>
<br>
<div id='text'></div>
</div>
<canvas id='canv'>
</canvas>
</body>

Making divs as high as possible without ther overlapping

I want to have something like this:
One main "wrapper" div that will have a few divs that are inline.
I can make this, but I don't know how to make the wrapper get as much space as possible, but without getting the scroll bar. And after that I would need to make those 4 inner divs as high as possible
Can I even achieve this with just CSS or would I need a bit of JS?
Flex is best for this here is codepen for you
codepen
.top{
width:100%;
height: 30px;
background-color: #00cdcd;
}
.container {
display: flex;
background-color: teal;
}
.child{
flex: 1;
border: 1px solid black;
}
You can achieve it in many ways only with css:
1st solution:
.wrapper {
display: flex;
}
.child {
flex: 1;
}
2nd solution:
.wrapper {
display: table;
}
.child {
display: table-cell;
}
You can dinamically size your div with JS:
window.onresize = function () {
var w = window.innerWidth;
var h = window.innerHeight;
document.getElementById('wrapper').style.height = w + 'px';
document.getElementById('wrapper').style.width = h + 'px';
}
so, you can have a precise control for each element in the DOM

Categories

Resources