Truncate string based on pixel width - javascript

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/

Related

Long words breaking in the middle of div using [...] [duplicate]

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.

Dynamically increase and decrease an input text box size depending on another selector width

I have an input text box which has some padding to it. I also have a wrapper class selector which is used next to that input text box. I am trying to remove set padding from the input text box and make that space dynamic so that the element size would (especially width) increase and decrease depending on the screen size (i.e. Mobile or Large view as large screen) without effecting the wrapper.
The text box looks like the following. a, c, d, e are buttons which appear dynamically. So the space for b here should expand if the there is only one button on the right and decrease if there are all the buttons on the right.
|____|________________________ |_____|_____|_____|
a b c d e
so the css class selectors that I have includes b and another one includes all the c, d, e (wrapper).
I assume this can't only be done through CSS. Any suggestion?
CSS:
.input {
position: relative;
width: 100%;
max-width: var(--grid-main-max-width);
padding: 1.188rem 2.9rem 1.188rem 4.5rem;
margin: 0;
font-size: 16px;
border: 1px solid var(--color-gray);
border-radius: 0.375rem;
outline: 0;
}
.wrapper {
position: absolute;
top: 0;
right: 1.5rem;
bottom: 0;
display: flex;
justify-content: center;
}
HTML
<div>
<input class="input">
<div class= "wrapper">
<button>c</button>
<button>d</button>
<button>e</button>
</div>
</div>
The solution only needed to count the width of the input text box and the wrapper and assign the difference as a padding to the right of the input text box. The following little change was added to an onInput event.
document.getElemendById("inputTextBox").style.paddingRight = document.getElemendById("searchFieldWrapper").clientWidth;
And also needed to use Media Queries for #media (--large-viewport) / #media (--medium-viewport) to assign different padding for the input. as #Scott Marcus mentioned in a comment.
Lets say you have div child blocks for those child elements or you can specify some class.
div:first-child:nth-last-child(1){
width: 100%;
}
div:first-child:nth-last-child(2),
div:first-child:nth-last-child(2) ~ div{
width: 50%;
}
div:first-child:nth-last-child(3),
div:first-child:nth-last-child(3) ~ div{
width: 33.3%;
}
div:first-child:nth-last-child(4),
div:first-child:nth-last-child(4) ~ div{
width: 25%;
}
//and so on
Source refer to here
Also if you want to modify other elements you can use
div:first-child:nth-last-child(2) > .someClass{
style:goesHere
}
(UPDATE: I figured out you used a wrapper element, and that a is'nt a label but a button. But this answer is easily adaptable to your question.)
You can use the calc function provided by CSS. Given this piece of HTML (I joined all the elements to remove side effects of the blank characters; we can fix it in an other way but I wanted to keep the answer simple):
<html>
<head>
</head>
<body>
<article id="demo">
<label>a</label><input type="text" placeholder="b" /><button>c</button><button>d</button><button>e</button>
</article>
</body>
</html>
This piece of CSS allow the input text element to fill the available space.
article#demo {
/* the width (80vw) includes border and padding */
width: 80vw;
}
article#demo label {
/* to make label resizable */
display: inline-block;
width: 30px;
}
article#demo button {
width: 20px;
margin: 0;
padding: 0;
background-color: #f0f0ff;
box-sizing: border-box;
/* see above */
}
article#demo input[type="text"] {
box-sizing: border-box;
/* text input width = 100% minus other items */
width: calc(100% - 30px - 3 * 20px);
}
You can set the width of article#demo using any unit (em, ex, etc.) it should work.
In your final design, use box-sizing:border-box to set the whole element, including borders and padding, within the CSS width. Otherwise, you'll have to adjust the calc parameter.
If you put left or right margins, count them too.
If you use font-dependent units (em, etc.), the same font-family and other font-related CSS entries have to be set - implicitly or not - for all the concerned elements.
Working fiddle with a little interactive test here.

Scrollbar on top of DIV and nowrap container

I'm familiar with this StackOverflow question & confirmed answer.
However, checking their fiddle here, I've noticed one thing I really really need. The container (aka div2 class element) doesn't have no-wrap property which I would really really need for my tables inside #container (nowrap for having tables in one row)
My code:
CSS:
.subnetTable {
width: 150px;
display: inline-table;
border:1px solid #E8E8E9;
margin: 2px;
padding: 2px;
white-space: normal;
}
#scroller_wrapper, #container_wrapper{
width: 98%; border: none 0px RED;
overflow-x: scroll; overflow-y:hidden;
}
#scroller_wrapper{height: 16px; }
#scroller { width: 500px; height: 16px; }
#container { width: 500px; overflow: auto;}
HTML:
<div id="scroller_wrapper">
<div id="scroller">
</div>
</div>
<div id="container_wrapper">
<div id="container">
<table class="subnetTable"><tr><td>12341234</td></tr></table>
<table class="subnetTable"><tr><td>12341234 123412341234 1234123412 34123412341 2341234123412 341234123 412341234</td></tr></table>
<table class="subnetTable"><tr><td>12341234</td></tr></table>
<table class="subnetTable"><tr><td>12341234 123412341 2341234123412 34123412341234 123412341 23412341234</td></tr></table>
<table class="subnetTable"><tr><td>12341234 1234123412 341234123412 34123412341234 123412341 23412341234</td></tr></table>
<table class="subnetTable"><tr><td>12341234 123412341 2341234123412 34123412341234 123412341 23412341234</td></tr></table>
</div>
</div>
JAVASCRIPT (jQuery):
// SCROLLBARS
$(function(){
$("#scroller_wrapper").scroll(function(){
$("#container_wrapper").scrollLeft($("#scroller_wrapper").scrollLeft());
});
$("#container_wrapper").scroll(function(){
$("#scroller_wrapper").scrollLeft($("#container_wrapper").scrollLeft());
});
});
// CONTAINER RESIZE
$(window).load(function () {
$('#scroller').css('width', ($(window).width() - 10) );
$('#container').css('width', ($(window).width() - 10) );
});
MY JsFiddle Code & the Problem:
click here.
The problem appears, when you add white-space: nowrap; to #container class. Instead of correct result, it creates another scrollbar at bottom which I wouldn't like. IT does move tables to one row but it doesn't create correct scrollbar at bottom or top anymore (replacing tables with only text doesn't work either).
Please help me out!
With you code as-is the solution is to put a <br> just before the fourth table or wrapping the first three and second three tables in a block level element such as a div.
Why?
Each table is inline - so setting #container to not wrap will make all the inline tables extend out to the right. Adding a break will force it to break as expected.
Note
Please consider using DIVs or some other semantic element rather than tables - your code does not appear to be tabular data.

jQuery: actual div width

I have two <div>, one nested into the other defined like this:
<div class="wrapper">
<div class="content">
[..] Content [..]
</div>
</div>
The css:
#div.wrapper
{
width: 660px;
overflow: hidden;
float: left;
}
#div.content
{
white-space: nowrap;
}
The effect is what i want, the content lays down horizontally within the inner but is hidden when it exceeds, (i then scroll it with jQuery).
Since I don't know the content of .content (nor it is predictable), I need to know the real width of it (defined by the content), but both .width() and .innerWidth() give me the same result that is 660 when first called (like the container div) and 660 + x when I call it after having scrolled it by setting a negative margin-left (x is the left shift set with the margin).
How to get the real, content dependent width of the element? Thanks
Divs by default take full width in their parent, so width will always be the width of the parent.
If you don't want that, you would use
#div.content
{
white-space: nowrap;
display: inline-block;
}
or possibly
#div.content
{
white-space: nowrap;
float: left;
}
as to turn the #div.content element into an inline or float element respectively, which only takes up as much space as it needs (i.e. not necessarily all the space that the parent provides). So that should work!
I wasn't sure why you used #div.* since that would mean you have an element with id div. If you mean to specify a div with a certain class, use div.class-name. Since a div resides as a child inside another div, it takes its parent's width. Setting the float to left allows it to expand outside this limit.
div.wrapper {
width: 660px;
overflow: hidden;
float: left;
}
div.content {
white-space: nowrap;
float: left;
}
Scrolling example: http://jsfiddle.net/9E8G7/6/
Here's a quick jQuery example I wrote, can be improved I'm sure. It scrolls until it reaches the end of the content.
$(document).ready(function(){
var content = $('.content');
var margin = 0;
var scrollFunc = function() {
margin--;
content.css('margin-left', margin);
var diff = content.width() - $('.wrapper').width();
if (margin > -diff) {
var scroll = setTimeout(scrollFunc, 10);
}
};
scrollFunc();
});
Did you try: jQuery.outerWidth(), I mean $(yourelement).outerWidth()?
Have you tried element.offsetWidth?

How can I make the first and second column of a table sticky

i have a div with with property
<div id="_body_container" style="height: 500px; overflow-x: auto; overflow-y: auto; ">
</div>`
inside this div i have the table which has class views-table, this table has 100% width which makes it's parent div:_body_container scrollable.I want to fix the first and the second column of this table sticky at their positions while the left and right scroll event happen for _body_container
structure is like:
Assuming each section is a <td> element...
CSS
table {
position: relative;
padding-left: (width-of-your-td-elements);
}
table td:first-of-type {
position: absolute;
left: 0;
}
Try that out. It's late and I'm drunk, so I'm not entirely sure on this. Oh, and keep in mind this is using CSS3, which isn't supported in <= IE8.
If this works, you could just add position:absolute; left:0; to a class and target the first element that way.
#vonkly is almost right about position:absolute. But you don't have to set left:0. With left:auto, you can spare position:relative too.
table {
padding-left: (width-of-your-td-elements);
}
table td:first-of-type {
position: absolute;
}

Categories