I want to disable browsers from adding anything other than HTML tags in my contenteditable div.
Unequivocally, no ifs, ands, buts or candied nuts.
Right now, Chrome in particular is really [irritating] me.
If I have a style set, say
p { font-size:1.2em; line-height:1.6em; }
It will say "oh, you must mean you want:"
<span style="font-size:1.2em; line-height:1.6em;">stuff</span>
I most certainly do not. In fact, I don't want it creating ANY span tags - ever. If I do, I will explicitly say so in my javascript, and not a moment before.
This is a highly frustrating problem, and I know contenteditable is still one of those use at your own peril web features chocked full of bugs and near-sighted "oh we should do..." by the browser developers that really just suck for making nice clean HTML.
Is there anything I can do here? I've already tamed "paste from Word" to fix MIcrosofts bastardized code soup, conquered most of execCommands shortcomings, but this one.. this one.
I'll be [flustered] if I cannot work around the browsers injection of unwanted and unnecessary markup after coming this far, but I'm concerned. Is there any hope?
Here's a function that can be applied to a contenteditable element, and it will ensure that the element ends with only a single child node of nodeName "#text".
var sanitizeContentEditable(elem) {
var textContent = [];
for (var i = 0; i < elem.childNodes.length; i++) {
var child = elem.childNodes[i];
textContent.push(child.textContent);
if (child.nodeName !== '#text') textContent.push('\n');
}
elem.innerHTML = textContent.join('');
};
This function simply takes the text content of every child element, appends a newline to the contents of non-text child elements, and joins all such strings together, thereby ensuring that the contenteditable element has only a single, text-type child.
Concerning "I don't want it creating ANY span tags - ever", what about using an Advanced Content Filter configured so that it does not allow span tags at all? That might work. Or configuring it to not allow font-size and line-height styles for spans, if you secretly do want spans to appear sometimes.
Related
We have a selector :empty that can match an element when it is completely empty:
<p></p>
But I have a condition where it might be empty, or it might contain line breaks or blank spaces:
<p> </p>
I found a solution for Firefox, :-moz-only-whitespace:
:empty { width: 300px; height: 15px; background: blue; }
:-moz-only-whitespace { width: 300px; height: 15px; background: orange; }
<p></p>
<p> </p>
<p>This is paragraph three.</p>
Is there a similar solution for other browsers?
PS: In JSFiddle
Lots of people missing the point of this question, which I've addressed in the following exposition, but for those just looking for the answer, I'm mirroring the last paragraph here:
Selectors 4 now redefines :empty to include elements that contain only whitespace. This was originally proposed as a separate pseudo-class :blank but was recently retconned into :empty after it was determined that it was safe to do so without too many sites depending on the original behavior. Browsers will need to update their implementations of :empty in order to conform to Selectors 4. If you need to support older browsers, you will have to go through the hassle of marking elements containing only whitespace or pruning the whitespace before or after the fact.
While the question depicts a <p> element containing a handful of regular space characters, which seems like an oversight, it is far more common to see markup where elements contain only whitespace in the form of indentation and blank lines, such as:
<ul class="items">
<li class="item">
<div>
<!-- Some complex structure of elements -->
</div>
</li>
<li class="item">
</li> <!-- Empty, except for a single line break and
indentation preceding the end tag -->
</ul>
Some elements, like <li> in the above example as well as <p>, have optional end tags, which can cause unintended side effects in DOM processing as well in the presence of inter-element whitespace. For example, the following two <ul> elements don't produce equivalent node trees, in particular the first one does not result in a li:empty in Selectors level 3:
li:empty::before { content: '(empty)'; font-style: italic; color: #999; }
<ul>
<li>
</ul>
<ul>
<li></li>
</ul>
Given that HTML considers inter-element whitespace to be transparent by design, it's not unreasonable to want to target such elements with CSS without having to resort to modifying the HTML or the application generating it (especially if you end up having to implement and test a special case just to do so). To that end, Selectors 4 now redefines :empty to include elements that contain only whitespace. This was originally proposed as a separate pseudo-class :blank but was recently retconned into :empty after it was determined that it was safe to do so without too many sites depending on the original behavior. Browsers will need to update their implementations of :empty in order to conform to Selectors 4. If you need to support older browsers, you will have to go through the hassle of marking elements containing only whitespace or pruning the whitespace before or after the fact.
#BoltClock provided a fantastic answer to this question, showing that this (currently, that is, working with CSS Specification 3) cannot be achieved by CSS alone.
#BoltClock mentioned that elements that are truly empty (which is a weird definition as explained) can be targeted by using the pseudo selector :empty. This pseudo selector is only available in CSS 3 and WILL NOT select elements that have only whitespace as content.
#BoltClock stated that the only way to clean up elements that have only whitespace as content is to fix the HTML, but that is not entirely correct. This can also be achieved through the implementation of Javascript.
KEEP IN MIND! The Javascript that I am offering to solve this issue may take a very long time to execute, so the best method is to clean up the raw HTML instead if possible. If that is not possible, then this may work as a solution, as long as you do not have too extensive of a DOM tree.
I'll walk through the steps of how to write the script yourself...
First of all, launch everything after page load.
This should be pretty obvious. You need to make sure that the DOM has fully loaded before running your script. Add an event listener for page load:
window.addEventListener("load", cleanUpMyDOM);
...and, of course, before that, create a function called cleanUpMyDOM. We will write the rest of our logic within this function.
Second, gather the elements that we are checking.
In our example we are going to check the entire DOM, but this is where our script can get VERY extensive and may make your page unresponsive. You may want to limit the amount of nodes you are iterating over.
We can grab the nodes in question by using the document.querySelectorAll. What's nice about this function is that it will level out the DOM tree and we won't have to recurse the children of each node.
var nodes = document.querySelectorAll("*");
As I said earlier, this code will grab EVERY DOM node, and that is probably NOT a good idea.
For example, I am working with WordPress, and some of the internal pages have some junk in them. Luckily, they are all p elements that are children of a div.body element, so I can change my selector to document.querySelectorAll("div.body p"), which will select only p elements that are children of my div.body element recursively. This will greatly optimize my script.
Third, iterate the nodes and find the empty ones.
We'll create a loop for the nodes array and check each node in it. We will then have to check to see if the node is empty. If it is empty, we'll apply a class to it called blank.
I'm just shooting from the hip here, so if you notice a mistake in this code, please let me know.
for(var i = 0; i < nodes.length; i++){
nodes[i].innerHTML = nodes[i].innerHTML.trim();
if(!nodes[i].innerHTML)
nodes[i].className += " blank";
}
I am sure that there is a cleaner way to write the loop above, but this should get the job done.
Lastly, all you need to do is target the blank elements with your CSS.
Add this rule to your stylesheet:
.blank {
display:none;
}
And there you have it! All of your "blank" nodes have been hidden.
For those who just want to jump ahead, here is the finished script:
function cleanUpMyDOM(){
var nodes = document.querySelectorAll("*");
for(var i = 0; i < nodes.length; i++){
nodes[i].innerHTML = nodes[i].innerHTML.trim();
if(!nodes[i].innerHTML)
nodes[i].className += " blank";
}
}
window.addEventListener("load", cleanUpMyDOM);
Once again, if you notice any issues with my code, please let me know in the comments below.
Hope this helps!
P.S. Many people may be wondering why you would want to do this, as it does feel like bad practice. I would avoid doing this, but I am in a situation where I am starting to consider it. The content of the pages on my site are created through a WYSIWYG editor. This content is created and modified constantly by the marketing team and I get pretty overwhelmed handling the support for their slip-ups. Its not my job to fix WordPress's WYSIWYG editor (nor would I ever want to), but I could write a very simple script that can handle some of the work for me. That definitely seems like the better answer to me, besides training the support team on managing their whitespace when making edits.
For anyone looking at the exact link: https://drafts.csswg.org/selectors-4/Overview.bs#the-empty-pseudo
And TL;DR:
Note: In Level 2 and Level 3 of Selectors, '':empty'' did not match
elements that contained only white space. This was changed so that
that-- given white space is largely collapsible in HTML and is
therefore used for source code formatting, and especially because
elements with omitted end tags are likely to absorb such white space
into their DOM text contents-- elements which authors perceive of as
empty can be selected by this selector, as they expect.
And :empty will consider spaces as empty from v4 onwards:
content nodes (such as [[DOM]] text nodes, and entity references) whose data has a non-zero length must be considered as affecting emptiness; comments, processing instructions, and other nodes must not affect whether an element is considered empty or not.
We have a selector :empty that can match an element when it is completely empty:
<p></p>
But I have a condition where it might be empty, or it might contain line breaks or blank spaces:
<p> </p>
I found a solution for Firefox, :-moz-only-whitespace:
:empty { width: 300px; height: 15px; background: blue; }
:-moz-only-whitespace { width: 300px; height: 15px; background: orange; }
<p></p>
<p> </p>
<p>This is paragraph three.</p>
Is there a similar solution for other browsers?
PS: In JSFiddle
Lots of people missing the point of this question, which I've addressed in the following exposition, but for those just looking for the answer, I'm mirroring the last paragraph here:
Selectors 4 now redefines :empty to include elements that contain only whitespace. This was originally proposed as a separate pseudo-class :blank but was recently retconned into :empty after it was determined that it was safe to do so without too many sites depending on the original behavior. Browsers will need to update their implementations of :empty in order to conform to Selectors 4. If you need to support older browsers, you will have to go through the hassle of marking elements containing only whitespace or pruning the whitespace before or after the fact.
While the question depicts a <p> element containing a handful of regular space characters, which seems like an oversight, it is far more common to see markup where elements contain only whitespace in the form of indentation and blank lines, such as:
<ul class="items">
<li class="item">
<div>
<!-- Some complex structure of elements -->
</div>
</li>
<li class="item">
</li> <!-- Empty, except for a single line break and
indentation preceding the end tag -->
</ul>
Some elements, like <li> in the above example as well as <p>, have optional end tags, which can cause unintended side effects in DOM processing as well in the presence of inter-element whitespace. For example, the following two <ul> elements don't produce equivalent node trees, in particular the first one does not result in a li:empty in Selectors level 3:
li:empty::before { content: '(empty)'; font-style: italic; color: #999; }
<ul>
<li>
</ul>
<ul>
<li></li>
</ul>
Given that HTML considers inter-element whitespace to be transparent by design, it's not unreasonable to want to target such elements with CSS without having to resort to modifying the HTML or the application generating it (especially if you end up having to implement and test a special case just to do so). To that end, Selectors 4 now redefines :empty to include elements that contain only whitespace. This was originally proposed as a separate pseudo-class :blank but was recently retconned into :empty after it was determined that it was safe to do so without too many sites depending on the original behavior. Browsers will need to update their implementations of :empty in order to conform to Selectors 4. If you need to support older browsers, you will have to go through the hassle of marking elements containing only whitespace or pruning the whitespace before or after the fact.
#BoltClock provided a fantastic answer to this question, showing that this (currently, that is, working with CSS Specification 3) cannot be achieved by CSS alone.
#BoltClock mentioned that elements that are truly empty (which is a weird definition as explained) can be targeted by using the pseudo selector :empty. This pseudo selector is only available in CSS 3 and WILL NOT select elements that have only whitespace as content.
#BoltClock stated that the only way to clean up elements that have only whitespace as content is to fix the HTML, but that is not entirely correct. This can also be achieved through the implementation of Javascript.
KEEP IN MIND! The Javascript that I am offering to solve this issue may take a very long time to execute, so the best method is to clean up the raw HTML instead if possible. If that is not possible, then this may work as a solution, as long as you do not have too extensive of a DOM tree.
I'll walk through the steps of how to write the script yourself...
First of all, launch everything after page load.
This should be pretty obvious. You need to make sure that the DOM has fully loaded before running your script. Add an event listener for page load:
window.addEventListener("load", cleanUpMyDOM);
...and, of course, before that, create a function called cleanUpMyDOM. We will write the rest of our logic within this function.
Second, gather the elements that we are checking.
In our example we are going to check the entire DOM, but this is where our script can get VERY extensive and may make your page unresponsive. You may want to limit the amount of nodes you are iterating over.
We can grab the nodes in question by using the document.querySelectorAll. What's nice about this function is that it will level out the DOM tree and we won't have to recurse the children of each node.
var nodes = document.querySelectorAll("*");
As I said earlier, this code will grab EVERY DOM node, and that is probably NOT a good idea.
For example, I am working with WordPress, and some of the internal pages have some junk in them. Luckily, they are all p elements that are children of a div.body element, so I can change my selector to document.querySelectorAll("div.body p"), which will select only p elements that are children of my div.body element recursively. This will greatly optimize my script.
Third, iterate the nodes and find the empty ones.
We'll create a loop for the nodes array and check each node in it. We will then have to check to see if the node is empty. If it is empty, we'll apply a class to it called blank.
I'm just shooting from the hip here, so if you notice a mistake in this code, please let me know.
for(var i = 0; i < nodes.length; i++){
nodes[i].innerHTML = nodes[i].innerHTML.trim();
if(!nodes[i].innerHTML)
nodes[i].className += " blank";
}
I am sure that there is a cleaner way to write the loop above, but this should get the job done.
Lastly, all you need to do is target the blank elements with your CSS.
Add this rule to your stylesheet:
.blank {
display:none;
}
And there you have it! All of your "blank" nodes have been hidden.
For those who just want to jump ahead, here is the finished script:
function cleanUpMyDOM(){
var nodes = document.querySelectorAll("*");
for(var i = 0; i < nodes.length; i++){
nodes[i].innerHTML = nodes[i].innerHTML.trim();
if(!nodes[i].innerHTML)
nodes[i].className += " blank";
}
}
window.addEventListener("load", cleanUpMyDOM);
Once again, if you notice any issues with my code, please let me know in the comments below.
Hope this helps!
P.S. Many people may be wondering why you would want to do this, as it does feel like bad practice. I would avoid doing this, but I am in a situation where I am starting to consider it. The content of the pages on my site are created through a WYSIWYG editor. This content is created and modified constantly by the marketing team and I get pretty overwhelmed handling the support for their slip-ups. Its not my job to fix WordPress's WYSIWYG editor (nor would I ever want to), but I could write a very simple script that can handle some of the work for me. That definitely seems like the better answer to me, besides training the support team on managing their whitespace when making edits.
For anyone looking at the exact link: https://drafts.csswg.org/selectors-4/Overview.bs#the-empty-pseudo
And TL;DR:
Note: In Level 2 and Level 3 of Selectors, '':empty'' did not match
elements that contained only white space. This was changed so that
that-- given white space is largely collapsible in HTML and is
therefore used for source code formatting, and especially because
elements with omitted end tags are likely to absorb such white space
into their DOM text contents-- elements which authors perceive of as
empty can be selected by this selector, as they expect.
And :empty will consider spaces as empty from v4 onwards:
content nodes (such as [[DOM]] text nodes, and entity references) whose data has a non-zero length must be considered as affecting emptiness; comments, processing instructions, and other nodes must not affect whether an element is considered empty or not.
I am trying to hide the following element in an automatically generated HTML document:
<p id="sitspagedesc" class="sitspagedesc">
</p>
In some pages, the <p> tag will contain an inner value but in others it can contain only spaces as shown in the example. I need to find a way of hiding this so that it is hidden using CSS only, as changing the HTML is not an option.
I have tried to hide it using
.sitspagedesc:empty
{
display:none;
}
but this does not work, presumably on the account of the spaces the element contains.
Does anyone have any good ideas?
Thanks :)
I don't think you can do it with pure CSS.
However with a little JavaScript you can do it.
var allParas = document.getElementsByTagName('p');
//filter by class name if desired...
for(var i=0;i<allParas.length;i++){
if(allParas[i].getElementsByTagName('*').length == 0){
allParas[i].style.display = 'none';
}
}
If you have access to jQuery it is a little easier to do the filtering with their built in selectors.
$('p.sitspagedesc').each(function(){
if($(this).children().length == 0){
$(this).hide();
}
});
If the desire is to mimic the functionality of the :empty selector except that whitespace is ignored, the accepted answer (by scunliffe) doesn't quite work. It only checks for child elements, and this doesn't account for text directly inside the selected element. For instance, <p>Hello World!</p> would be treated as empty because it has no child elements even though it does contain non-whitespace text.
My solution uses the jQuery.trim() function to remove leading and trailing whitespace from the .text() value which contains the combined text contents of the selected element and its descendants. So the selected element is hidden if it contains no non-whitespace text and no child elements. As with the :empty selector, HTML comments are not counted as content since they are not reflected in either the .text() or .children() values.
$('p.sitspagedesc').each(function(){
if($.trim($(this).text()) == '' && $(this).children().length == 0){
$(this).hide();
}
});
See the Fiddle at https://jsfiddle.net/TNTitan89/crejkbxq/.
The :empty selector is indeed very strict. An element containing a space is not considered empty. So there are two solutions
Modify the output. Trim the values you output or minimize the HTML, so those spaces are removed. Or even better: don't render those elements at all. I think that is the best option, because it both minimizes traffic and gives you a solution that works without Javascript.
Use Javascript to find those elements. I'm not aware of tricks that let you find these elements easily, so you may have to run through all elements, searching for ones you consider empty and add a class to those elements. This may be very slow, especially on low end devices. Also, it will only hide the elements once the script is run, so on page load the element will be visible for a short while until it is hidden. It may be clear that this isn't the ideal solution.
Maybe you can combine both. The :empty selector is a CSS3 selector and is not yet supported by IE8 and before, so a Javascript fallback might be a good idea for those browsers, unless you can fix the server side scripting so that the empty elements are not rendered at all, or are given your special class during rendering, so no Javascript is needed.
Answer: Not yet, but it's drafted.
https://drafts.csswg.org/selectors-4/#the-blank-pseudo
...and —at least for Mozilla— there's already a prefixed implementation... :-moz-only-whitespace:
http://jsfiddle.net/peayLrv3/
There's no way to detect empty elements in pure CSS (as yet). If Javascript isn't an option, is there anything you can do server-side to manipulate the HTML before it reaches the browser?
Here is my solution which I just implemented for a client using jQuery 1.5.x - you might have to adjust the //skip empty tags but which are valid regular expression string.
$('*:only-child:empty').each(
function(index) {
var currentElement = $(this);
// skip singleton tags
if(/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i.test(currentElement.get(0).tagName) == true) {
return
}
// skip empty tags but which are valid
if(/^(?:textarea)$/i.test(currentElement.get(0).tagName) == true) {
return
}
while (currentElement.parent().children().length == 1) {
currentElement = currentElement.parent();
}
// so 0 or more children for the parent then we hide it
// we will never have more then 0 children though the :empty takes care of that
console.log('hidding: ' + currentElement);
currentElement.hide()
}
);
While not a standard, Firefox has ":-moz-only-whitespace".
Also, for some "future proofing", css-tricks mentions a :blank selector that will be part of the CSS Selectors Level 4 draft. While no current browser supports it, it is a possibility.
css:
.sitspagedesc:empty
{
display:none;
}
jquery:
$('.sitspagedesc').html(function(){
// empty element
return $.trim($(this).html());
});
You could use:
p.sitspagedesc {
content: " ";
display: none;
}
Unless you randomly have multiple spaces in there...
First of all, I've seen this question IE Compatibility issue, but I have a slightly different issue:
I have a span with display: block and I want to put a H2 tag inside it. Will this look good in every (major) browser?
The answer on the previously stated link, was to make the H2 display:inline, which I don't want, and also I don't want to replace the span with a div, because I would have to change a lot of CSS.
PS. I don't want my HTML to validate (using the validator), I just want it to work.
When you say "I don't want my HTML to validate (using the validator), I just want it to work." You're making a very big mistake. If HTML doesn't validate, there's no telling what might happen. The standards are there for a reason.
Replace the span with a div, you don't need display block on the div as it has this by default. A span is an inline level element, whereas an H2 is block level. An inline element cannot contain a block level element (true up to HTML5, where you can wrap block level elements in anchors)
This will validate, and will work!
It works on current browsers (including old versions), and it is unlikely that this will change. Errors like this are common enough to make it rather pointless to browser vendors to change their error recovery mechanisms. HTML5 is about to make the error recovery rules mandatory.
On the other hand, what is the point of using span with display: block, instead of using div? And CSS should be written so that it does not depend on specific choices of markup elements where different choices could make sense; for example, as a rule, .foo is a better selector than span.foo.
Any extensive change to markup or style sheets has a nonnegligible risk of causing problems even when the change is a such an improvement and a simple one. (For example, a global search and replace often does too much, or misses something.) This could be a reason for continuing the use of invalid markup, in cases where it has minimal risk in practice.
we can solve IE second line problem first
h2{}
h2 span{ float:right;}
Correct way in all browsers
<h2><span>sub content</span> Heading</h2>
wrong way ie browser
<h2>Heading<span>sub content</span></h2>
How can you collapse div and p -elements if they are empty by CSS such that the tags are not shown for the user in the source code?
These elements are in my source code although they are empty.
This causes bugs with the rest of the site.
I run this unsuccessfully
div.unsuccessful,
div.unsuccessful p {
visibility: collapse;
}
This suggests me that you apparently cannot do such a thing by CSS.
Perhaps, other tool such as PHP is needed.
CSS has no influence on the actual HTML code, so this cannot be done. In the (upcoming) CSS3, you could stop them from being rendered using the :empty pseudo-class:
div:empty {
display: none;
}
That is of course not the same thing, but it would probably fix the problems you're having.. if only browsers actually supported it, which they don't, yet.
The best option is to remove them at the server, using a scripting language like PHP. I suppose you could do it client-side with JavaScript, but that's a horrible solution, imo.
That being said, what problems are you having with these empty tags? Why are they there in the first place? It seems to me that some redesign is in order, to prevent the unnecessary tags from being generated in the first place.
Also, be careful. Empty tags are not always meaningless. In fact, removing every empty <div> out there can be considered harmful.
Yes, if you want to stop them being in the source, you'll need to make the appropriate considerations in your server code. Alternatively, you can also set the HTML in JavaScript, but it's not the recommended approach for this problem, probably. You may like to say a bit more though, about what you're trying to do.
Visibility property is intended to set whether the content of the object is displayed or not. It won't remove the element from inside the DOM.
collapse New for Windows Internet
Explorer 8
Used in tables to remove rows and columns; for all other elements, same as hidden.
Also why do you want to do this?
div { display: none; }
removes an element completely from the page.
It'll still show up in your source code, no matter what you do with CSS. The browser combines the HTML source and the CSS directives into a displayable page, the original source is not modified. CSS can only influence the visual display of elements, it can not alter elements. To actually remove elements from the DOM you'll need Javascript (or not put the elements there in the first place).
Yes, you will have to use server side processing to show or not-show some code to the user:
if ($showAdminLink) {
echo "<p>Admin panel</p>";
}
No matter what tricks you try, anything done on the client side (HTML, Javascript, CSS) can be altered and played with.