Is it possible to wrap indented code on a web page the way it's done in a code editor? See the screenshot comparison below to better understand what I mean:
pre-wrap on a web page:
Wrapping of indented lines in a code editor:
What I am implying is that the indented lines maintain indentation even after wrapping. This doesn't seem to happen on web pages. Is there a CSS property that does this? (JavaScript would be fine too.)
NOTE: I am not talking about code highlighting here. It's about indentation of wrapped lines.
If this matters — this is how I am showing code blocks on my web pages:
<pre><code>if ( is_page() && $post->post_parent ) {
return $post->post_parent;
} else {
return false;
}
</code></pre>
...and the white-space: pre-wrap; style is applied on pre tag.
Algorithm
Get the contents of the element, and generate a list of all lines.
Use the element to measure the width of a space character.
Create a document fragment (for optimal performance!).
Loop through all lines. For each line:
Count the number of preceeding white space.
Create a block-level element (such as <div>).
Set the marginLeft (or paddingLeft, if you wish) property to the product of the size of a single space and the number of prefixed spaces.
Append The contents of the line (left trimmed).
Replace the contents of the actual element with the fragment.
Code (demo: http://jsfiddle.net/YPnhX/):
/**
* Auto-indent overflowing lines
* #author Rob W http://stackoverflow.com/u/938089
* #param code_elem HTMLCodeElement (or any element containing *plain text*)
*/
function autoindent(code_elem) {
// Grab the lines
var textContent = document.textContent === null ? 'textContent' : 'innerText';
var lines = code_elem[textContent].split(/\r?\n/),
fragment = document.createDocumentFragment(),
dummy, space_width, i, prefix_len, line_elem;
// Calculate the width of white space
// Assume that inline element inherit styles from parent (<code>)
dummy = document.createElement('span');
code_elem.appendChild(dummy);
// offsetWidth includes padding and border, explicitly override the style:
dummy.style.cssText = 'border:0;padding:0;';
dummy[textContent] = ' ';
space_width = dummy.offsetWidth;
// Wipe contents
code_elem.innerHTML = '';
for (i=0; i<lines.length; i++) {
// NOTE: All preceeding white space (including tabs is included)
prefix_len = /^\s*/.exec(lines[i])[0].length;
line_elem = fragment.appendChild(document.createElement('div'));
line_elem.style.marginLeft = space_width * prefix_len + 'px';
line_elem[textContent] = lines[i].substring(prefix_len);
}
// Finally, append (all elements inside) the fragment:
code_elem.appendChild(fragment);
}
Browser compatibility
IE8 + (IE7- doesn't support white-space:pre-wrap)
Chrome 1+
Firefox 3+
Safari 3+
Opera 9+ (previous versions untested)
Notes
In this example, I calculated the width of a space (U+0020) character. The similar method is used if you want to calculate different values for other white-space characters.
Follow-up to the previous note: To account for tabs, you have to take a hard route, which degrades performance. For each line, set the contents of the dummy (appended to code_elem!) to the prefixed white space, then calculate the width using .offsetWidth.
Each time, the element is rendered. For hundreds of lines, this method may cause a spike in the CPU usage. Don't ever use tabs to display code in a web page!
The autoindent function assumes that the contents of a element is plain text.
Related
This question already has answers here:
Insert ellipsis after specific number of lines
(2 answers)
Closed 7 years ago.
How can I make text-overflow: ellipsis work in multi-line text without applying white-space: nowrap, with pure javascript, without including any jQuery library and without specifying the number of lines?
I made alot of search for this and I couldn't find any simple and pure Javascript solution. So I ended up making my own :
function ellipsis(el){
var elheight = el.offsetHeight;
var eltxt = el.innerHTML;
while (elheight < el.scrollHeight){
eltxt = eltxt.substring(0, eltxt.length - 1);
el.innerHTML = eltxt + '...';
}
}
elheight contains the actual height of the box (using offsetHeight)
scrollHeight gives us the height of the content of the box (including the overflowing text)
This function stores the content of the box inside eltxt and then keep checking if the actual size of the box is still smaller than its content, and each time it removes 1 character from eltxt and replace the content of the element by the new value of eltxt plus the three points.
It works on Google chrome 4+, IE 8+, Firefox 3+, Safari 4+, and (I think) all Opera versions.
It's pretty simple and easy, but all I found on google is other long jQuery libraries with functions that you need to provide the number of lines you want in order for them to work etc..
EDIT
However, this solution is NOT the exact equivalent of text-overflow: ellipsis as it doesn't hide the extra text, it removes it. So if your box has got some animations and its dimensions are supposed to change (get wider for example) then your text will not reappear as it was. In that case I recommend storing the text of the box outside of the function and then you run the function each time the dimensions of the box change by giving it the text of the box as 2nd parameter :
var myElementsText = myElement.innerHTML;
function ellipsis(el, txt){
var elheight = el.offsetHeight;
var eltxt = txt;
while (elheight < el.scrollHeight){
eltxt = eltxt.substring(0, eltxt.length - 1);
el.innerHTML = eltxt + '...';
}
}
function changeSize(el){
el.ClassName = 'new_class_with_different_dimensions';
function ellipsis(el, myElementsText);
}
Now if you run
changeSize(myElement);
The box will have its original text with the new ellipsis.
I have a CMS which uses ckeditor at its heart. I have just upgraded the version of ckeditor to the latest and now when you align an image left or right in the editor it puts in the inline style rather than the 'align' attribute.
While the inline style is not a problem I need the 'align' attribute to remain so that I can apply padding to images through CSS programmatically without needing to add styles to each image in the editor (as the users of the CMS would not be technically competent to do this).
I have been successful in making a function to find images with the style attribute and assigning an align attribute. Then updating the editor using 'setData' and when I 'getData' the update seems to remain. However, at somepoint during the save process it seems to remove this. Any ideas on where this is, or how to add both align and style align at the same time.
After alot more Googling ironically I found the answer on here:
CKEditor align images instead of float
Why it didnt come up in searches I have no idea. This certainly did the trick, though I removed the lines relating to width and height and removed the replacement of the 'float' css attribute as this caused the WYSIWYG to not pickup the styling. Apart from that its all good!
UPDATE: I found there were instances where this didn't quite work with CKeditor 4 and found this small edition to the code fixed it.
element.forEach = function(){};
element.writeChildrenHtml = function(){};
See: http://vibhajadwani.wordpress.com/2011/07/18/how-to-remove-image-style-property-from-ckeditor/
So the complete code block is as follows:
CKEDITOR.on('instanceReady', function( ev )
{
// Ends self closing tags the HTML4 way, like <br>.
// See: https://stackoverflow.com/questions/4466185/ckeditor-align-images-instead-of-float
// Mod added for CKE 4
// See: http://vibhajadwani.wordpress.com/2011/07/18/how-to-remove-image-style-property-from-ckeditor/
ev.editor.dataProcessor.htmlFilter.addRules(
{
elements:
{
$: function( element )
{
// Output dimensions of images as width and height
if( element.name == 'img' )
{
var style = element.attributes.style;
if( style )
{
// Get the width from the style.
var match = /(?:^|\s)width\s*:\s*(\d+)px/i.exec( style ),
width = match && match[ 1 ];
// Get the height from the style.
match = /(?:^|\s)height\s*:\s*(\d+)px/i.exec( style );
var height = match && match[ 1 ];
// Get the float from the style.
match = /(?:^|\s)float\s*:\s*(\w+)/i.exec( style );
var align = match && match[ 1 ];
if( align )
{
element.attributes.align = align;
}
}
element.forEach = function(){};
element.writeChildrenHtml = function(){};
}
return element;
}
}
});
});
As a quick fix (for those of you who don't want to dig into the CKEditor files) you can actually style the images with CSS even if there is no align or class added to the image with the * property.
example:
img[style*=left] {
float: left;
margin: 0px 20px 10px 0px;
}
img[style*=right] {
float: right;
margin: 0px 0px 10px 20px;
}
You can use the [style*=] property to check the inline style="" attribute for the word 'left' or 'right' which gets applied to images by CKEditor, then style them any way you want. Clients will still use the alignment dropdown to select whether they want the image to float to the left or the right, so they won't notice any change at all.
We ran into the same issue as #Eth, however our entire ckEditor had been minified and was nearly uneditable. Hope this offers another solution!
Make sure filter is "Full HTML", Limit allowed HTML Tags is unchecked, Convert media tags to mark ups is checked and in Filter processing order Convert Media tags to markup is at top. Voila it is done
Is it possible to get the width (using javascript or jQuery) of a float-affected element? When text is being pushed over due to a floating image is it possible to get its position and true width? I have attached an image to explain better.
Code example,
<div>
<img style="...float: left"/>
<h1>A title!</h1>
<p>Text!</p>
<h1>New header added.</h1>
</div>
Picture
I need to find the width starting from the arrow, (the gray box is the image)(the dotted line is the width according to Firefox inspect mode).
I would like to avoid changing all the elements display types if possible.
Thank you!
I'm a little late to the party, but I had a similar problem and came up with a solution which (so far) seems to work in all instances of this issue. I like this solution because as far as I can tell, it works independent of the floating element - all you need is the element whose true width/position you want to get, nothing more. I've done it in pure Javascript for speed purposes, but it can easily be streamlined with jQuery and a separate CSS Stylesheet if you so choose.
//Get the rendered bounding box for the content of any HTMLElement "el"
var getLimits = function(el) {
//Set a universal style for both tester spans; use "!important" to make sure other styles don't mess things up!
var testerStyle = 'width: 0px!important; overflow: hidden!important; color: transparent!important;';
//Create a 'tester' span and place it BEFORE the content
var testerStart = document.createElement('SPAN');
testerStart.innerHTML = '|';
var testerFloat = ' float: left!important;';
testerStart.setAttribute('style', testerStyle + testerFloat);
//Insert testerStart before the first child of our element
if (el.firstChild) {
el.insertBefore(testerStart, el.firstChild);
} else {
el.appendChild(testerStart);
}
//Create a 'tester' span and place it AFTER the content
var testerEnd = document.createElement('SPAN');
testerEnd.innerHTML = '|';
testerFloat = ' float: right!important;';
testerEnd.setAttribute('style', testerStyle + testerFloat);
el.appendChild(testerEnd);
//Measure the testers
var limits = {
top: testerStart.offsetTop,
bottom: testerEnd.offsetTop + testerEnd.offsetHeight,
left: testerStart.offsetLeft,
right: testerEnd.offsetLeft
}
//Remove the testers and return
el.removeChild(testerStart);
el.removeChild(testerEnd);
return limits;
};
So, in your case, the code would just be:
var paragraphBoundingBox = getLimits($('div>p').get(0));
A couple things to note:
1) The float direction would be reversed if you are using an RTL language
2) All of the four edge positions in the output object are relative to the el.offsetParent - use this handy function can find their positions relative to the document.
First of all, the "full width" is exactly the true width.
You can watch this picture, it can help you understand why the true width and true position of the affected element is the way firefox tells you.
http://i.stack.imgur.com/mB5Ds.png
To get the width of inline text where it's pushed right by the float image, there's no good way except using the full width minus the float image's width.
var w = $('p').width()
- $('img').width()
- $('img').css('margin-left').replace("px", "")
- $('img').css('margin-right').replace("px", "")
- $('img').css('padding-left').replace("px", "")
- $('img').css('padding-right').replace("px", "")
- $('img').css('border-left-width').replace("px", "")
- $('img').css('border-right-width').replace("px", "");
In a generated html page, we have a fixed size area (lets say 200 x 300) in which we need to fit in as much text as possible (like a regular paragraph of text), and if it doesn't fit, remove an appropriate number of characters and append "..." to the end.
The text is NOT in a fixed sized font, and although we are using one specific font for this text, a "generic" solution would obviously be preferred.
This looked interesting, but I'm thinking it would be very slow with this function being called for several items on a page - http://bytes.com/topic/javascript/answers/93847-neatly-truncating-text-fit-physical-dimension
The solution can use an intermix of html, css, js, and php as needed.
Suggestions on approaches are more than welcome!
I'd say that the solution you found is the best. It is, for instance, used for this jQuery plugin which autoresizes textareas as you enter text into it. I took the concept and rewrote it with jQuery for this simple test here: http://jsfiddle.net/ZDr5K/
var para = $('#para');
var height = 200;
while(para.height() >= height){
var text = para.text();
para.text(text.substring(0, text.length - 4) + '...');
}
Possible improvements would include right trimming and removing the period if the last character is a full stop. Removing word by word would also be more readable/slightly faster.
As for the function running multiple times, that would be unavoidable. The only thing you can really do with CSS here is to use :after to append the ellipses, but even that should be avoided for cross-compatibility problems.
Set the element dimensions via CSS and its overflow to "hidden".
Then, find out with this function, if the element's content is overflowing (via):
// Determines if the passed element is overflowing its bounds,
// either vertically or horizontally.
// Will temporarily modify the "overflow" style to detect this
// if necessary.
function checkOverflow(el)
{
var curOverflow = el.style.overflow;
if ( !curOverflow || curOverflow === "visible" )
el.style.overflow = "hidden";
var isOverflowing = el.clientWidth < el.scrollWidth
|| el.clientHeight < el.scrollHeight;
el.style.overflow = curOverflow;
return isOverflowing;
}
Now, in a loop remove text and check until it is not overflowing anymore. Append an ellipsis character (String.fromCharCode(8230)) to the end, but only if it was overflowing.
To avoid any flickering effects during that operation, you can try working on a hidden copy of the element, but I'm not sure if the browsers do the necessary layout calculations on an element that's not visible. (Can anyone clarify that?)
I need a Jquery script to truncate a text paragraph by line (not by character count).
I would like to achieve an evenly truncated text-block. It should have a "more" and "less" link to expand and shorten the text paragraph. My text paragraph is wrapped in a div with a class, like this:
<div class="content">
<h2>Headline</h2>
<p>The paragraph Text here</p>
</div>
The closest solution i could find on SOF is the one below (but it`s for textarea element and does not work for me):
Limiting number of lines in textarea
Many thanks for any tips.
Ben
For a basic approach, you could take a look at the line-height CSS property and use that in your calculations. Bear in mind that this approach will not account for other inline elements that are larger than that height (e.g. images).
If you want something a bit more advanced, you can get the information about each line using getClientRects() function. This function returns a collection of TextRectangle objects with width, height and offset for each one.
See this answer here for an example (albeit an unrelated goal) of how getClientRects() works.
Update, had a bit of time to come back and update this answer with an actual example. It's basic, but you get the idea:
http://jsbin.com/ukaqu3/2
A couple of pointers:
The collection returned by getClientRects is static, it won't update automatically if the containing element's dimensions change. My example works around this by capturing the window's resize event.
For some strange standards-compliance reason that I'm not understanding, the element you call getClientRects on must be an inline element. In the example I have, I use a container div with the text in another div inside with display: inline.
I made this little jQuery code to allow me truncate text blocks by line (via CSS classes), feel free to use and comment it.
Here is the jsFiddle, which also include truncate functions by char count or word count. You can see that currently, resize the window won't refresh the block display, I'm working on it.
/*
* Truncate a text bloc after x lines
* <p class="t_truncate_l_2">Lorem ipsum magna eiusmod sit labore.</p>
*/
$("*").filter(function () {
return /t_truncate_l_/.test($(this).attr('class'));
}).each(function() {
var el = $(this);
var content = el.text();
var classList = el.attr('class').split(/\s+/);
$.each(classList, function(index, item){
if(/^t_truncate_l_/.test(item)) {
var n = item.substr(13);
var lineHeight = parseInt(el.css('line-height'));
if(lineHeight == 1 || el.css('line-height') == 'normal')
lineHeight = parseInt(el.css('font-size')) * 1.3;
var maxHeight = n * lineHeight;
var truncated = $.trim(content);
var old;
if(el.height() > maxHeight)
truncated += '...';
while(el.height() > maxHeight && old != truncated) {
old = truncated;
truncated = truncated.replace(/\s[^\s]*\.\.\.$/, '...');
el.text(truncated);
}
}
});
});
why not make the p element with overflow: hidden; give fixed line height, caluclate the height of the div so id contains exactly the number of lines you require and the only change the height of the p from javascript.
p{
overflow:hidden;
line-height:13px;
height:26px; /* show only 2 rows */
}
<script type="text/javascript">
function seeMoreRows(){
$(p).height("52px");
}
</script>
I made a small module that works with pure text content, no nested tags and no css-padding on the text-containing element is allowed (but this functionality could easily be added).
The HTML:
<p class="ellipsis" data-ellipsis-max-line-count="3">
Put some multiline text here
</p>
The Javascript/Jquery:
( function() {
$(document).ready(function(){
store_contents();
lazy_update(1000);
});
// Lazy update saves performance for other tasks...
var lazy_update = function(delay) {
window.lazy_update_timeout = setTimeout(function(){
update_ellipsis();
$(window).one('resize', function() {
lazy_update(delay);
});
}, delay);
}
var store_contents = function(){
$('.ellipsis').each(function(){
var p = $(this);
p.data('ellipsis-storage', p.html());
});
}
var update_ellipsis = function(){
$('.ellipsis').each(function(){
var p = $(this);
var max_line_count = p.data('ellipsis-max-line-count');
var line_height = p.html(' ').outerHeight();
var max_height = max_line_count*line_height;
p.html(p.data('ellipsis-storage'));
var p_height = p.outerHeight();
while(p_height > max_height){
var content_arr = p.html().split(' ');
content_arr.pop();
content_arr.pop();
content_arr.push('...');
p.html(content_arr.join(' '));
p_height = p.outerHeight();
}
});
}
} )();
I hope you like it!
If you used a monospaced font, you'd have a shot at this working, as you'd have a good idea how many letters fit onto each line, for an element of a defined width. However, if a word breaks across lines, then this might get tricky..
e: found another question which is basically what you're after - they didn't really have a resolution either, but to my mind, the line-height and element height seems closest.
"How can I count text lines inside a dom element"
tl;dr - set a height on your container div and then use the jQuery dotdotdot plugin
Was about to make #Andy E's awesome example into a plugin, but then realized https://github.com/BeSite/jQuery.dotdotdot could pull this off. Our use case is we want to show one line on desktop widths and two lines on mobile/tablet.
Our CSS will just set the container div to the equivalent of one or two line-height's accordingly and then the dotdotdot plugin appears to handle the rest.