I have a big filename that I'm cropping using css text-overflow: ellipsis.
<style>
#fileName {
width: 100px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
</style>
<div id="fileName"> This is the big name of my file.txt</div>
So I have this output
This is the bi...
But I want to preserve the file extension and have something like this
This is the... le.txt
Is it possible only using CSS?
Since my files are always txt, I've tried to use text-overflow: string, but it looks like it only works on Firefox:
text-overflow: '*.txt';
Here is a clean CSS solution using the data-* attribute and two ::after pseudo-elements. I also added an optional hover and show all text (the #fileName::after pseudo element needs to be removed when the full text is shown).
Example 1
#fileName {
position: relative;
width: 100px;
}
#fileName p {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
#fileName:after {
content: attr(data-filetype);
position: absolute;
left: 100%;
top: 0;
}
/*Show on hover*/
#fileName:hover {
width: auto
}
#fileName:hover:after {
display: none;
}
<div id="fileName" data-filetype="txt">
<p>This is the big name of my file.txt</p>
</div>
Going further — hiding the appended filetype when the filename is short
The #fileName p::after is given a background color that matches the background of the text. This covers the ".txt" when the filenames are short and therefore not cut off with overflow: hidden.
Note the padding-right: 22px, this pushes the ".txt" beyond the ellipsis.
Refer to examples 2 and 3 below for different methods with different browser support for each. It doesn't seem to be possible to hide the ".txt" happily in all browsers.
Example 2
Browser Compatibility: Chrome and Firefox.
The #fileName p::after is given a background color that matches the background of the text. This covers the ".txt" when the filenames are short and therefore not cut off with overflow: hidden.
Note the padding-right on each of the ::after pseudo-elements. padding-right: 22px pushes the ".txt" beyond the ellipsis and padding-right: 100% gives the covering pseudo-element its width. The padding-right: 100% doesn't work with Edge or IE 11.
#fileName {
position: relative;
width: 122px;
}
#fileName::after {
content: attr(data-filetype);
position: absolute;
right: 0;
top: 0;
}
#fileName p {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
padding-right: 22px;
}
#fileName p::after {
content: '';
background: #FFF;
position: relative;
padding-right: 100%;
z-index: 1;
}
/*Show on hover*/
#fileName:hover {
width: auto;
}
/*Hide .txt on hover*/
#fileName:hover::after {
display: none;
}
<div id="fileName" data-filetype=".txt">
<p>This is the big name of my file.txt</p>
</div>
<div id="fileName" data-filetype=".txt">
<p>Short.txt</p>
</div>
Example 3
Browser Compatibility: IE 11, Edge and Chrome.
The content: ... unholy amount of ... on #fileName p::after gives it width. This, along with display: inline-block, is currently the only method that works on the Edge browser / IE 11 as well as Chrome. The display: inline-block breaks this method on Firefox and the .txt is not covered on short filenames.
#fileName {
position: relative;
width: 122px;
}
#fileName::after {
content: attr(data-filetype);
position: absolute;
right: 0;
top: 0;
padding-right: 10px; /*Fixes Edge Browser*/
}
#fileName p {
white-space: nowrap;
overflow: hidden;
padding-right: 22px;
text-overflow: ellipsis;
}
#fileName p::after {
content: '.........................................................................................................................';/*Fixes Edge Browser*/
background: #FFF;
position: relative;
display: inline-block;/*Fixes Edge Browser*/
z-index: 1;
color: #FFF;
}
/*Show on hover*/
#fileName:hover {
width: auto
}
#fileName:hover::after {
display: none;
}
<div id="fileName" data-filetype=".txt">
<p>This is the big name of my file.txt</p>
</div>
<div id="fileName" data-filetype=".txt">
<p>Short.txt</p>
</div>
This is the best I can come up with... It might be worthwhile trying to clean up the leading edge of the second span...
CSS
#fileName span {
white-space: nowrap;
overflow: hidden;
display:inline-block;
}
#fileName span:first-child {
width: 100px;
text-overflow: ellipsis;
}
#fileName span + span {
width: 30px;
direction:rtl;
text-align:right;
}
HTML
<div id="fileName">
<span>This is the big name of my file.txt</span>
<span>This is the big name of my file.txt</span>
</div>
http://jsfiddle.net/c8everqm/1/
Here is another suggestion that worked well for me:
<div style="width:100%;border:1px solid green;display:inline-flex;flex-wrap:nowrap;">
<div style="flex: 0 1 content;text-overflow: ellipsis;overflow:hidden;white-space:nowrap;"> Her comes very very very very very very very very very very very very very very very very very very very long </div>
<div style="flex: 1 0 content;white-space:nowrap;"> but flexible line</div>
</div>
Here's a solution that uses flexbox, and is dynamic, (e.g. works when the user resizes the browser window). Disadvantage is that the text after the ellipsis has a fixed size, so you can't put the ellipsis in the exact middle of the text.
CSS
.middleEllipsis {
margin: 10px;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
}
.start {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex-shrink: 1;
}
.end {
white-space: nowrap;
flex-basis: content;
flex-grow: 0;
flex-shrink: 0;
}
HTML
<div class="middleEllipsis">
<div class="start">This is a really long file name, really long really long really long</div><div class="end">file name.txt</div>
</div>
Resize the right-hand side boxes on jsfiddle to see the effect:
https://jsfiddle.net/L9sy4dwa/1/
If you're willing to abuse direction: rtl, you can even get the ellipsis right in the middle of the text with some small changes to your CSS:
.middleEllipsis {
margin: 10px;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
}
.middleEllipsis > .start {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex-shrink: 1;
}
.middleEllipsis > .end {
white-space: nowrap;
flex-basis: content;
flex-grow: 0;
flex-shrink: 1;
align: right;
overflow: hidden;
direction: rtl;
}
You can see an animated gif of what this looks like on https://i.stack.imgur.com/CgW24.gif.
Here's a jsfiddle showing this approach:
https://jsfiddle.net/b8seyre3/
I tried some of those CSS approach but the problem is if the text is short, you will get "short text short text" instead of "short text".
So I went with CSS + JS approach.
JS (I edited Jeremy Friesen's to fix some cases):
const shrinkString = (originStr, maxChars, trailingCharCount) => {
let shrinkedStr = originStr;
const shrinkedLength = maxChars - trailingCharCount - 3;
if (originStr.length > shrinkedLength) {
const front = originStr.substr(0, shrinkedLength);
const mid = '...';
const end = originStr.substr(-trailingCharCount);
shrinkedStr = front + mid + end;
}
return shrinkedStr;
}
HTML:
<div>
<h5>{shrinkString("can be very long of short text", 50, 15)} </h5>
</div>
CSS:
div {
width: 200px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
I hope it helps. Sorry for the format. This is my first answer on SO.
JavaScript option:
var cropWithExtension = function(value, maxChars, trailingCharCount) {
var result = value;
if(value.length > maxChars){
var front = value.substr(0, value.length - (maxChars - trailingCharCount - 3));
var mid = "...";
var end = value.substr(-trailingCharCount);
result = front + mid + end;
}
return result;
}
var trimmedValue = cropWithExtension("This is the big file.txt", 21, 6);
Input ---This is a very very very very very big file.txt
To truncate the above file name use the below javascript
Output ---This is a very...big file.txt
var selectedFileName = getItemSelected();//Input
$scope.referenceFileName =getItemSelected();
var len = selectedFileName.length;
if(len > 30){
selectedFileName = selectedFileName.substr(0,15)+'... '+selectedFileName.substr(len-15,15);
}
$scope.fileName = selectedFileName;
**
Note:
**Pass the $scope.referenceFileName in the json---back end
$scope.fileName this would be---front end
The accept answer is good. Although for Browser Compatibility, you could do the detection for truncate or not. Make the whole CSS conditional.
const wrap = document.getElementById('filenameText');
if (wrap.offsetWidth >= wrap.scrollWidth) {
this.truncation = false;
}
<div
:data-filetype="data-filetype"
:class="[truncation && 'truncateFilenamClass']"
>
I found out the css solutions quite buggy and hard to maintain, since you need to add attributes or elements to separate text.
I built a quite straight forward Javascript that handles it. Send your text and max length of the text and you get the text truncated in the middle back.
const truncateMiddle = (text, maxCharacters) => {
const txtLength = text.length; // Length of the incoming text
const txtLengthHalf = maxCharacters ? Math.round(maxCharacters / 2) : Math.round(txtLength / 2); // set max txtHalfLength
return text.substring(0, (txtLengthHalf -1)).trim() + '...' + text.substring((txtLength - txtLengthHalf) + 2, txtLength).trim() //Return the string
}
truncateMiddle('Once opon a time there was a little bunny', 10);
Returns: Once...nny
Cons? Sure, it need more functionality to be responsive.
CSS is good, but I think you must do it by JavaScript for more accurate results.
Why?
Because, with JS You can control number of first and last texts of words.
This is just 2 lines of JavaScript code to crop string as per you define:-
let fileName=document.getElementById('fileName')
fileName.innerHTML=fileName.innerHTML.substring(1, 10)+'...'+fileName.innerHTML.slice(-2)
<div id="fileName"> This is the big name of my file.txt</div>
also, you can choose first n words, instead of first few letter/characters with JS, as per you want.
whose JS code is this:-
let fileName=document.getElementById('fileName')
let Words=fileName.innerHTML.split(" ")
let i=0;
fileName.innerHTML=''
Words.forEach(e => {
i++
if(i<5)
fileName.innerHTML+=e+' '
});
fileName.innerHTML+='...'
<div id="fileName"> This is the big name of my file.txt</div>
For a solution that works with liquid layouts I came up with something that uses flexbox. Obvious drawback is that three elements are needed. Obvious advantage: If there is enough room everything will be shown. Depending on circumstances an additional white-space rule for the paragraph might be needed as well as some min-width for the first span.
<p><span>Long text goes in here except for the</span><span>very end</span></p>
p {display:flex}
p span:first-child {flex-shrink:1; text-overflow: ellipsis; overflow: hidden}
ADDENDUM: Strictly speaking, the flex-shrink is not even necessary because it is the default behaviour of the flex-items anyway. This is not so in IE10, however. Prefixing is necessary, too in this case.
Related
I have coded a script (with the help of a user here) which allows me to expand a selected div and make the other divs behave accordingly by stretching equally to fit the remaining space (except the first one which width is fixed).
And here is a picture of what I want to achieve:
For that I use flex and transitions.
It works well, but the jQuery script specifies a "400%" stretch value (which is great for testing).
Now I would like the selected div to expand/shrink to exactly fit the content instead of the "400%" fixed value.
I have no idea how I could do that.
Is it possible ?
I tried to clone the div, fit it to the content, get its value and then use this value to transition BUT this means I have an initial width in percentages but a target value in pixels. That doesn't work.
And if I convert the pixel value in percentages, then the result doesn't exactly fit the content for whatever reason.
In all cases, this seems a bit of a complicated way to achieve what I want anyway.
Isn't there any flex property that could be transitioned in order to fit the content of a selected div?
Here is the code (edited/simplified since for a better read) :
var expanded = '';
$(document).on("click", ".div:not(:first-child)", function(e) {
var thisInd =$(this).index();
if(expanded != thisInd) {
//fit clicked fluid div to its content and reset the other fluid divs
$(this).css("width", "400%");
$('.div').not(':first').not(this).css("width", "100%");
expanded = thisInd;
} else {
//reset all fluid divs
$('.div').not(':first').css("width", "100%");
expanded = '';
}
});
.wrapper {
overflow: hidden;
width: 100%;
margin-top: 20px;
border: 1px solid black;
display: flex;
justify-content: flex-start;
}
.div {
overflow: hidden;
white-space: nowrap;
border-right: 1px solid black;
text-align:center;
}
.div:first-child {
min-width: 36px;
background: #999;
}
.div:not(:first-child) {
width: 100%;
transition: width 1s;
}
.div:not(:first-child) span {
background: #ddd;
}
.div:last-child {
border-right: 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Click on the div you want to fit/reset (except the first div)
<div class="wrapper">
<div class="div"><span>Fixed</span></div>
<div class="div"><span>Fluid (long long long long long text)</span></div>
<div class="div"><span>Fluid</span></div>
<div class="div"><span>Fluid</span></div>
</div>
Here is the jsfiddle:
https://jsfiddle.net/zajsLrxp/1/
EDIT: Here is my working solution with the help of you all (sizes updated on window resize + number of divs and first column's width dynamically calculated):
var tableWidth;
var expanded = '';
var fixedDivWidth = 0;
var flexPercentage = 100/($('.column').length-1);
$(document).ready(function() {
// Set width of first fixed column
$('.column:first-child .cell .fit').each(function() {
var tempFixedDivWidth = $(this)[0].getBoundingClientRect().width;
if( tempFixedDivWidth > fixedDivWidth ){fixedDivWidth = tempFixedDivWidth;}
});
$('.column:first-child' ).css('min-width',fixedDivWidth+'px')
//Reset all fluid columns
$('.column').not(':first').css('flex','1 1 '+flexPercentage+'%')
})
$(window).resize( function() {
//Reset all fluid columns
$('.column').not(':first').css('flex','1 1 '+flexPercentage+'%')
expanded = '';
})
$(document).on("click", ".column:not(:first-child)", function(e) {
var thisInd =$(this).index();
// if first click on a fluid column
if(expanded != thisInd)
{
var fitDivWidth=0;
// Set width of selected fluid column
$(this).find('.fit').each(function() {
var c = $(this)[0].getBoundingClientRect().width;
if( c > fitDivWidth ){fitDivWidth = c;}
});
tableWidth = $('.mainTable')[0].getBoundingClientRect().width;
$(this).css('flex','0 0 '+ 100/(tableWidth/fitDivWidth) +'%')
// Use remaining space equally for all other fluid column
$('.column').not(':first').not(this).css('flex','1 1 '+flexPercentage+'%')
expanded = thisInd;
}
// if second click on a fluid column
else
{
//Reset all fluid columns
$('.column').not(':first').css('flex','1 1 '+flexPercentage+'%')
expanded = '';
}
});
body{
font-family: 'Arial';
font-size: 12px;
padding: 20px;
}
.mainTable {
overflow: hidden;
width: 100%;
border: 1px solid black;
display: flex;
margin-top : 20px;
}
.cell{
height: 32px;
border-top: 1px solid black;
white-space: nowrap;
}
.cell:first-child{
background: #ccc;
border-top: none;
}
.column {
border-right: 1px solid black;
transition: flex 0.4s;
overflow: hidden;
line-height: 32px;
text-align: center;
}
.column:first-child {
background: #ccc;
}
.column:last-child {
border-right: 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span class="text">Click on the header div you want to fit/reset (except the first one which is fixed)</span>
<div class="mainTable">
<div class="column">
<div class="cell"><span class="fit">Propriété</span></div>
<div class="cell"><span class="fit">Artisan 45</span></div>
<div class="cell"><span class="fit">Waterloo 528</span></div>
</div>
<div class="column">
<div class="cell"><span class="fit">Adresse</span></div>
<div class="cell"><span class="fit">Rue du puit n° 45 (E2)</span></div>
<div class="cell"><span class="fit">Chaussée de Waterloo n° 528 (E1)</span></div>
</div>
<div class="column">
<div class="cell"><span class="fit">Commune</span></div>
<div class="cell"><span class="fit">Ixelles</span></div>
<div class="cell"><span class="fit">Watermael-Boitsfort</span></div>
</div>
<div class="column">
<div class="cell"><span class="fit">Ville</span></div>
<div class="cell"><span class="fit">Marche-en-Famenne</span></div>
<div class="cell"><span class="fit">Bruxelles</span></div>
</div>
<div class="column">
<div class="cell"><span class="fit">Surface</span></div>
<div class="cell"><span class="fit">120 m<sup>2</sup></span></div>
<div class="cell"><span class="fit">350 m<sup>2</sup></span></div>
</div>
</div>
And here is a fully fledged example at work (styles + padding + more data):
https://jsfiddle.net/zrqLowx0/2/
Thank you all !
It is possible to solve it using max-width and calc().
First, replace width: 100% with flex: 1 for the divs in CSS, so they will grow, which is better in this case. In addition, use transition for max-width.
Now, we have to store some relevant values:
The amount of divs that will be animated (divsLength variable) - 3 in this case.
The total width used for the fixed div and the borders (extraSpace variable) - 39px in this case.
With those 2 variables, we can set a default max-width (defaultMaxWidth variable) to all the divs, as well as using them later. That is why they are being stored globally.
The defaultMaxWidth is calc((100% - extraSpace)/divsLength).
Now, let's enter the click function:
To expand the div, the width of the target text will be stored in a variable called textWidth and it will be applied to the div as max-width. It uses .getBoundingClientRect().width (since it return the floating-point value).
For the remaining divs, it is created a calc() for max-width that will be applied to them.
It is: calc(100% - textWidth - extraScape)/(divsLength - 1).
The calculated result is the width that each remaining div should be.
When clicking on the expanded div, that is, to return to normal, the default max-width is applied again to all .div elements.
var expanded = false,
divs = $(".div:not(:first-child)"),
divsLength = divs.length,
extraSpace = 39, //fixed width + border-right widths
defaultMaxWidth = "calc((100% - " + extraSpace + "px)/" + divsLength + ")";
divs.css("max-width", defaultMaxWidth);
$(document).on("click", ".div:not(:first-child)", function (e) {
var thisInd = $(this).index();
if (expanded !== thisInd) {
var textWidth = $(this).find('span')[0].getBoundingClientRect().width;
var restWidth = "calc((100% - " + textWidth + "px - " + extraSpace + "px)/" + (divsLength - 1) + ")";
//fit clicked fluid div to its content and reset the other fluid divs
$(this).css({ "max-width": textWidth });
$('.div').not(':first').not(this).css({ "max-width": restWidth });
expanded = thisInd;
} else {
//reset all fluid divs
$('.div').not(':first').css("max-width", defaultMaxWidth);
expanded = false;
}
});
.wrapper {
overflow: hidden;
width: 100%;
margin-top: 20px;
border: 1px solid black;
display: flex;
justify-content: flex-start;
}
.div {
overflow: hidden;
white-space: nowrap;
border-right: 1px solid black;
text-align:center;
}
.div:first-child {
min-width: 36px;
background: #999;
}
.div:not(:first-child) {
flex: 1;
transition: max-width 1s;
}
.div:not(:first-child) span {
background: #ddd;
}
.div:last-child {
border-right: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
Click on the div you want to fit/reset (except the first div)
<div class="wrapper">
<div class="div"><span>Fixed</span></div>
<div class="div"><span>Fluid (long long long long text)</span></div>
<div class="div"><span>Fluid</span></div>
<div class="div"><span>Fluid</span></div>
</div>
This approach behaves dynamically and should work on any resolution.
The only value you need to hard code is the extraSpace variable.
You need to deal with the width or calc functions. Flexbox would have a solution.
To make all divs equal (not first one) we use flex: 1 1 auto.
<div class="wrapper">
<div class="div"><span>Fixed</span></div>
<div class="div"><span>Fluid (long long long long text)</span></div>
<div class="div"><span>Fluid</span></div>
<div class="div"><span>Fluid</span></div>
</div>
Define flex rules for your normal div and selected div. transition: flex 1s; is your friend. For selected one we don't need flex grow so we use flex: 0 0 auto;
.wrapper {
width: 100%;
margin-top: 20px;
border: 1px solid black;
display: flex;
}
.div {
white-space: nowrap;
border-right: 1px solid black;
transition: flex 1s;
flex: 1 1 auto;
}
.div.selected{
flex: 0 0 auto;
}
.div:first-child {
min-width: 50px;
background: #999;
text-align: center;
}
.div:not(:first-child) {
text-align: center;
}
.div:last-child {
border-right: 0px;
}
div:not(:first-child) span {
background: #ddd;
}
Add selected class each time when the user clicks a div. You can also use toggle for the second click so you can save selected items in a map and you can show multiple selected items (not with this code example of course).
$(document).on("click", ".div:not(:first-child)", function(e) {
const expanded = $('.selected');
$(this).addClass("selected");
if (expanded) {
expanded.removeClass("selected");
}
});
https://jsfiddle.net/f3ao8xcj/
After a few trial versions, this seems to be my shortest and most straighforward solution.
All that essentially needs to be done is have Flexbox stretch the <div> elements to their limits by default, but when <span> clicked, constraint the stretch of the <div> to <span> width ...
pseudo code:
when <span> clicked and already toggled then <div> max-width = 100%, reset <span> toggle state
otherwise <div> max-width = <span> width, set <span> toggle state
I have split the CSS into a 'relevant mechanism' and 'eye-candy only' section for easy reading (and code recyling).
The code is heavily commented, so not much text here...
Quirk Somehow there is an extra delay in the transition when switching the div from max-width: 100% to max-width = span width. I've checked this behaviour in Chrome, Edge, IE11 and Firefox (all W10) and all seem to have this quirk. Either some browser internal recalc going on, or maybe the transition time is used twice ('feels like'). Vice Versa, oddly enough, there is no extra delay.
However, with a short transition time (e.g. 150ms, as I am using now) this extra delay is not/hardly noticable. (Nice one for another SO question...)
$(document).on('click', '.wrapper>:not(.caption) span', function (e) {
// Save the current 'toggle' status
var elemToggled = e.target.getAttribute('toggled');
// Set parent max-width to maximum space or constraint to current child width
e.target.parentElement.style.maxWidth =
(elemToggled=="true") ? '100%' : parseFloat(window.getComputedStyle(e.target).width) + 'px';
// (Re)set child toggle state
e.target.setAttribute('toggled', (elemToggled=="true") ? false : true);
});
/*********************/
/* Wrapper mechanism */
/*********************/
.wrapper { /* main flexible parent container */
display : flex; /* [MANDATORY] Flexbox Layout container, can't FBL without */
flex-wrap: nowrap; /* [MANDATORY] default FBL, but important. wrap to next line messes things up */
flex-grow: 1; /* [OPTIONAL] Either: if '.wrapper' is a FBL child itself, allow it to grow */
width : 100%; /* [OPTIONAL] or : full parent width */
/* (Maybe a fixed value, otherwise redundant here as 'flex-grow' = 1) */
}
/* generic rule */
.wrapper>* { /* flexed child containers, being flexible parent containers themselves */
flex-grow : 1; /* [MANDATORY] important for this mechanism to work */
overflow: hidden; /* [MANDATORY] important, otherwise output looks messy */
display: flex; /* [MANDATORY] for FBL stretching */
justify-content: center;/* [MANDATORY] as per SOQ */
max-width : 100%; /* [OPTIONAL/MANDATORY], actually needed to trigger 'transition' */
}
/* exception to the rule */
.wrapper>.fixed { /* fixed child container */
flex-grow: 0; /* [MANDATORY] as per SOQ, don't allow grow */
}
/******************/
/* Eye-candy only */
/******************/
.wrapper {
border: 1px solid black;
}
.wrapper>:not(.fixed) {
transition: max-width 150ms ease-in-out;
}
.wrapper>:not(:last-child){
border-right: 1px solid black;
}
/* generic rule */
.wrapper>*>span {
white-space: nowrap;
background-color: #ddd;
}
/* exception to the rule */
.wrapper>.fixed>span {
background-color: #999;
}
/* debug helper: show all elements with outlines (put in <body>) */
[debug="1"] * { outline: 1px dashed purple }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<div class="fixed"><span>Fixed</span></div>
<div><span>Fluid (long long long long long text)</span></div>
<div><span>Fluid</span></div>
<div><span>Fluid</span></div>
</div>
UPDATE
New version that resets all other <div>. I truly hate the jumpiness, but that is due to Flexbox stretching and the transition value. Without transition no jumps visible. You need to try out what works for you.
I only added document.querySelectorAll() to the javascript code.
$(document).on('click', '.wrapper>:not(.caption) span', function (e) {
var elemToggled = e.target.getAttribute('toggled'); // Toggle status
var elemWidth = parseFloat(window.getComputedStyle(e.target).width); // Current element width
// reset ALL toggles but 'this'...
document.querySelectorAll('.wrapper>:not(.caption) span')
.forEach( function (elem,idx) {
if (elem != this){
elem.parentElement.style.maxWidth = '100%';
elem.setAttribute('toggled',false);
};
});
// Set parent max-width to maximum space or constraint to current child width
e.target.parentElement.style.maxWidth =
(elemToggled=="true") ? '100%' : parseFloat(window.getComputedStyle(e.target).width) + 'px';
// (Re)set child toggle state
e.target.setAttribute('toggled', (elemToggled=="true") ? false : true);
});
/*********************/
/* Wrapper mechanism */
/*********************/
.wrapper { /* main flexible parent container */
display : flex; /* [MANDATORY] Flexbox Layout container, can't FBL without */
flex-wrap: nowrap; /* [MANDATORY] default FBL, but important. wrap to next line messes things up */
flex-grow: 1; /* [OPTIONAL] Either: if '.wrapper' is a FBL child itself, allow it to grow */
width : 100%; /* [OPTIONAL] or : full parent width */
/* (Maybe a fixed value, otherwise redundant here as 'flex-grow' = 1) */
}
/* generic rule */
.wrapper>* { /* flexed child containers, being flexible parent containers themselves */
flex-grow : 1; /* [MANDATORY] important for this mechanism to work */
overflow: hidden; /* [MANDATORY] important, otherwise output looks messy */
display: flex; /* [MANDATORY] for FBL stretching */
justify-content: center;/* [MANDATORY] as per SOQ */
max-width : 100%; /* [OPTIONAL/MANDATORY], actually needed to trigger 'transition' */
}
/* exception to the rule */
.wrapper>.fixed { /* fixed child container */
flex-grow: 0; /* [MANDATORY] as per SOQ, don't allow grow */
}
/******************/
/* Eye-candy only */
/******************/
.wrapper {
border: 1px solid black;
}
.wrapper>:not(.fixed) {
transition: max-width 150ms ease-in-out;
}
.wrapper>:not(:last-child){
border-right: 1px solid black;
}
/* generic rule */
.wrapper>*>span {
white-space: nowrap;
background-color: #ddd;
}
/* exception to the rule */
.wrapper>.fixed>span {
background-color: #999;
}
/* show all elements with outlines (put in <body>) */
[debug="1"] * { outline: 1px dashed purple }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<div class="fixed"><span>Fixed</span></div>
<div><span>Fluid (long long long long long text)</span></div>
<div><span>Fluid</span></div>
<div><span>Fluid</span></div>
</div>
If you need only one row, there is a simpler solution based on this code : https://jsfiddle.net/jpeter06/a5cu52oy/
with the css flex modified for columns instead of rows :
.container {
flex-grow: 10;
flex-shrink: 0;
flex-basis: auto;
display: flex;
flex-direction: row;
width: 100%;
}
.item { min-width:30px;
flex-basis:30px;
overflow-x:hidden;
transition: flex-basis 500ms ease-in-out;
}
.expanded {
flex-basis: 20em;
}
html, body {
width: 100%; height: 100%;
margin: 0; padding: 0; border: 0; overflow: hidden;
}
html code :
<div class="container">
<div class="item" style="background: red">a<br/>a<br/>a<br/>a<br/>a<br/>a<br/>a<br/>a<br/></div>
<div class="item" style="background: green">b<br/>b<br/>b<br/>b</div>
<div class="item" style="background: blue">c<br/>c<br/>c<br/>c</div>
</div>
JS code :
$(document).ready(function() {
$(".item").click(function() {
$(this).addClass('expanded');
$(".item").not(this).each(function() {
$(this).removeClass("expanded");
});
});
});
I am using Angular grid to achieve a Pintrest-like fluid layout of items on my page. It works fine in general but in soem special cases it produces an empty space on my page. It seems the position of item7 is not calculated correctly
I am using AngularGrid from here
My code is quite similar to what they recommend in their documentation except that I am not using an image but a custom component inside the ng-repeat.
so it looks like this:
angular.module('app').controller('demo', demo);
function demo($rootScope, $scope, apiService, angularGridInstance) {
var self=this;
this.noOfProjects=8;
this.firstNo=0;
this.loadedProjects;
this.totalProjects;
apiService.searchProjects($rootScope.userId, self.noOfProjects, self.firstNo).then(function(response) {
console.log(response.data);
self.loadedProjects = response.data.matches;
self.totalProjects = response.data.header.totalCount;
console.log(self.loadedProjects);
$scope.pics=self.loadedProjects;
});
}
.dynamic-grid{
position: relative;
}
.angular-grid > *{
opacity: 1;
}
.dynamic-grid.angular-grid{
display: block;
}
.grid {
position: absolute;
list-style: none;
background: #ffffff;
box-sizing: border-box;
-moz-box-sizing : border-box;
overflow: hidden;
}
.grid-img {
width: 100%;
vertical-align: middle;
background-color: #fff;
}
.grid-img.img-loaded{
visibility: visible;
opacity: 1;
}
<div class="" data-ng-controller="demo">
<ul class="dynamic-grid" angular-grid="pics" grid-width="300" gutter-size="10" angular-grid-id="gallery" refresh-on-img-load="false" >
<li data-ng-repeat="project in pics" class="grid" data-ng-clock>
<project-info-min-grid project="project" class="grid-img" ></project-info-min-grid>
</li>
</ul>
</div>
Can anyone help with this problem?
Ok. a bit of research showed that this was actually cased by this css on some parts of the content of each item:
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
Instead I am now shortening the string in javascript like this:
var maxLength=100;
if(self.project.description){
if(self.project.description.length > maxLength) {
self.project.description = self.project.description.substring(0,maxLength-1)+"...";
}
}
and everything works fine. Just in case someone else runs into this problem...
In my application I want to check if there is text-overflow(ellipsis). And for that I'm check e.offsetWidth < e.scrollWidth but they return equal value, means offsetWidth returns wrong value. Here is the my implementation:
CSS:
.less{
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.complete{
word-wrap: break-word;
}
.collapse{
color:blue;
font-size:13px;
cursor:pointer;
}
.hideElement{
display: none;
}
#seemore:after{
content: " \000BB";
}
#seeless:before{
content:"\000AB\ ";
}
#description {
width: 100%;
overflow: hidden;
}
HTML:
<div id = "description">
<div id="lessdescription" class="less"></div>
<div id="seemore" class="collapse"></div>
<div id="seeless" class="collapse hideElement"></div>
</div>
JS
function manageReportDescription() {
var descriptionElement = document.getElementById("lessdescription");
return descriptionElement.offsetWidth < descriptionElement.scrollWidth;
}
I want to use pure JS properties (without relying on jQuery)
You Javascript code is okay, the issue is because there is no text in your div so offsetWidth is equal to screen width and scrollWidth is also equal to screen width, so the comparison will return false because the two elements compared have the same value screen width , try adding some text in your lessdescription div
I'm trying to make a string fits in a determined area (td) without breaking line. The problem is: It needs to ...
Fit while resizing
Fit in N kinds of resolution
Add ... at the end when needed (and don't when not needed)
Accept the change of font-size, font-weight, and font-family
Based on the answers on Novon's question, I made the following code:
CSS (// Just adding some styles to break the line)
.truncated { display:inline-block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
jQuery (// Find all td that contains a .truncated element. Getting the real width (by removing the content and adding it again) of the td. Applying, if needed, minWidth on the td)
jQuery('.truncated').closest('td').each(function() {
var text = jQuery(this).text();
var widthOriginal = jQuery(this).width();
var widthEmpty = jQuery(this).text('').width();
jQuery(this).text(text);
if(widthOriginal >= widthEmpty){
var width = (parseInt(widthEmpty) - 10) + 'px';
jQuery(this).css('maxWidth', width);
jQuery(this).text(text + '...');
}
});
the result (as expected from the above code) is:
but it should be:
I was thinking, maybe try to find the first line of the string and remove the rest but didn't find a way to do that (and it's a lot of "workaround" for my taste). Is there a better way to do that?
Single line text truncation can be easily achieved using css text-overflow property, which is supported by all major browsers, including IE since version 6.
The thing is that text-overflow alone doesn't do much. It only defines what will happen when there is text overflowing the container. So in order to see results, we first need to make the text overflow, by forcing it to a single line. It is also important to set the overflow property of the container:
.truncated {
display: block;
white-space: nowrap; /* forces text to single line */
overflow: hidden;
text-overflow: ellipsis;
}
jsFiddle example: https://jsfiddle.net/x95a4913/
text-overflow documentation: https://developer.mozilla.org/en-US/docs/Web/CSS/text-overflow
You can do it with pure CSS, see this link for reference:
line clampin
Add those to your css:
.truncated {
display: -webkit-box;
-webkit-line-clamp: 1; // amount of line you want
-webkit-box-orient: vertical;
}
Or you can try clamp.js
https://github.com/josephschmitt/Clamp.js
text-overflow: ellipsis
seems a pure CSS solution
http://www.w3schools.com/cssref/css3_pr_text-overflow.asp
Wrap the text twice to achieve this:
<style type="text/css">
.relative_wrap {
height: 1em;
position: relative;
}
.absolute_wrap {
position: absolute;
left: 0px;
right: 0px;
bottom: 0px;
overflow: hidden;
text-overflow: ellipsis;
top: 0px;
white-space: nowrap;
}
</style>
<table>
<tbody>
<tr>
<td>
<div class="relative_wrap">
<div class="absolute_wrap">
LONG TEXT HERE
</div>
</div>
</td>
</tr>
</tbody>
</table>
Since you use jQuery, it is easy:
$('.truncated').wrap('<div class="relative_wrap"><div class="absolute_wrap"></div></div>');
If you set table layout to fixed and the td overflow as hidden, you could prepend an ellipsis as a float-right div when the td's scroll width is greater than its client width.
Here's the CSS, which includes styles to prevent bleed-through on the table:
table {
table-layout:fixed;
white-space:nowrap;
width:500px;
border-spacing: 0px;
border-collapse: separate;
}
td {
overflow:hidden;
border:1px solid #ddd;
padding:0px;
}
.hellip {
padding-left:0.2em;
float:right;
background:white;
position:relative;
}
jQuery:
$('td').each(function() {
if($(this)[0].scrollWidth > $(this).width()) {
$(this).prepend('<div class="hellip"">…</div>');
}
});
Demo at: http://jsfiddle.net/5h798ojf/3/
I have an element with image and text,
Fiddle. Note: Resize preview enough to make grid big enough.
Here is my CSS:
.gridster .gs-w .item{
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.gridster .gs-w .item .obj{
background-color: #00A9EC;
}
.gridster .gs-w .item .itemIcon {
height: 100%;
width: 100%;
float:left;
overflow: hidden;
z-index: 10;
}
.gridster .gs-w .item .itemIcon {
background-image: url(http://icons.iconarchive.com/icons/dakirby309/windows-8-metro/256/Apps-Calendar-Metro-icon.png);
background-repeat:no-repeat;
background-size:contain;
align-content: center;
}
.gridster .gs-w .item .itemText{
display: block;
width: 100%;
position: relative;
margin-right: 0px;
right: 0px;
text-align: right;
z-index: 9;
}
.gridster .gs-w .item .itemText a{
vertical-align: center;
text-align:right;
color:white;
padding-right: 10%;
font-size: 14px;
font-weight: 600;
text-decoration:none;
font-family: 'Segoe UI';
}
I want to show text when element is expanded, and hide when element is collapsed, I think I can achieve it by CSS, but it's not yet clear what is wrong.
and here it is collapsed
advise some CSS code, in case if possible to make in CSS.
You can hook into resize.resize.
By checking data attribute data-sizex you get how many columns the cell spans. By this you can expand the init function to the following:
Sample fiddle.
public.init = function (elem) {
container = elem;
// Initialize gridster and get API reference.
gridster = $(SELECTOR, elem).gridster({
shift_larger_widgets_down: true,
resize: {
enabled: true,
resize: function (e, ui, $widget) {
var cap = $widget.find('.itemText');
// Hide itemText if cell-span is 1
if ($widget.attr('data-sizex') == 1) {
cap.hide();
} else {
cap.show();
}
}
}
}).data('gridster');
hookWidgetResizer();
}
Or cleaner, and likely preferable. Split it out to own function and say something like:
resize: capHide
Sample fiddle.
If you rather go for the solution proposed by your updated images, one way is to tweak the CSS on resize, using your resize_widget_dimensions function. Sure this can be done better, but as a starter you can have this:
Sample fiddle.
this.$widgets.each($.proxy(function (i, widget) {
var $widget = $(widget);
var data = serializedGrid[i];
this.resize_widget($widget, data.size_x, data.size_y);
// Find itemText
var $it = $widget.find('.itemText');
// Set CSS values.
$it.css({width:this.min_widget_width, left:this.min_widget_width});
}, this));
Challenge is that the gridster is a very fluid cake where a lot of the dimensions and positioning is done by JavaScript rather then pure CSS. Anyhow, the above should give a direction on how to tweak it, and might even be good enough ;)
As a final treat you can resize the font according to cell size. I'm not sure how to best find the size you want as you divide the space between icon/image and text. But something like this:
Sample fiddle.
Where you have a hidden span to measure text:
<span id="font_sizer"></span>
With CSS:
#font_sizer {
position: absolute;
font-family:'Segoe UI';
visibility: hidden;
}
And font measure by:
function szFont(w, t) {
var s = 1, $fz = $('#font_sizer');
$fz.text(t);
$fz.css('fontSize', s + 'px');
while ($fz.width() < w - 2)
$fz.css('fontSize', ++s + 'px');
return s;
}
You can set font size as:
var fontSize = szFont(this.min_widget_width - 10, 'Objects');
Where this.min_widget_width - 10 is the part where you set size available for text. Then you can say:
var $it = $widget.find('.itemText');
$it.css({fontSize: fontSize + 'px', width:this.min_widget_width, left:this.min_widget_width});
Other notes:
You have a typo in:
var container,
grister, // <<-- Missing 'd' in gridster
resizeTimer;
In extensions you have
var data = serializedGrid[i];
this.resize_widget($widget, data.sizex, data.sizey);
however a console.log of data show:
data.size_x
data.size_y
not sure how this fits in. The data attribute uses sizex / y but data property from serialize, (on object), it uses size_x / y with underscore.
I think you are looking for media query:
#media all and (max-width: 760px) {
.gridster .gs-w .item .itemText {
display: none;
}
}
Example
You can hide text by using below type of CSS
.gridster .gs-w .item .itemText a.hide-text {
text-align: left;
text-indent: -99999px;
display: inline-block;
}
now whenever you want to hide text you need to add this class i.e. hide-text on anchor element Objects and vice versa to show text remove class
basically you need to try and figure out best possible solution to fit all requirements Good luck