DOCTYPE breaks style.display - javascript

I have a (legacy) JS function, that shows or hides child nodes of argument element. It is used in mouseover and mouseout event handlers to show-hide img tags.
The function looks like this:
function displayElem(elem, value, handlerRoot){
try{
var display = 'inline';
if(!value)
display = 'none';
if(handlerRoot)
elem.style.display = display;
var childs = elem.childNodes;
for (i = 0; i < childs.length; i++){
if(childs[i].nodeType == Node.ELEMENT_NODE){
childs[i].style.display = display;
alert("Node "+childs[i].tagName+" style set to " +childs[i].style.display);
}
}
}catch(e){
alert('displayElem: ' + e);
}
}
Here, value and handlerRoot are boolean flags.
This function works perfectly, if target html page has no doctype. Adding any doctype (strict or transitional) breaks this. Alert shows style has been set to the right value, but child elements are not displayed.
Would be good, if this function could work with any DOCTYPE.
Image (a child node of elem) is initialized like this (perhaps something is wrong here?):
var img = new Image();
img.style.cssText =
'background: transparent url("chrome://{appname}/content/dbutton.png") right top no-repeat;' +
'position: relative;' +
'height:18px;'+
'width:18px;'+
'display:none;';

JavaScript doesn't really work over plain HTML but on the DOM tree generated by the browser. Thus the DOCTYPE does not have a direct influence on JavaScript but on the way the browser handles invalid HTML and CSS.
I think the first step is to clean-up the HTML and make sure it's valid, esp. that tags are used in allowed places and properly nested. That will guarantee that the generated node tree is the same no matter the rendering mode.
You can also use your favourite browser tool (such as Firebug) the inspect the real tree and make sure nodes are placed where you think they are.

Update:
I wonder if when dealing with a document in standards mode (the document has a DOCTYPE), Firefox is inserting an implied element that it doesn't insert in backward-compat mode (no DOCTYPE), and so the image isn't an immediate child of elem but instead a child of this implied element that's then a child of elem; so you won't see the image in elem.childNodes. Walking through the code in a debugger is the best way to tell, but failing that, alert the tagName of each of the child nodes you're iterating through in the loop.
For example, with this markup:
<table id='theTable'>
<tr><td>Hi there</td></tr>
</table>
...Firefox will insert a tbody element, so the DOM looks like this:
<table id='theTable'>
<tbody>
<tr><td>Hi there</td></tr>
</tbody>
</table>
...but it won't be that specific example unless the DOCTYPE is a red herring, because I just tested and Firefox does that even in backward-compat mode. But perhaps you were testing two slightly different documents? Or perhaps it does it with some elements only in standards mode.
Original:
Not immediately seeing the problem, but I do see two issues:
i isn't declared in the function, and so you're falling prey to the Horror of Implicit Globals. Since your alert is showing the correct value, I can't see why that would be the problem.
url(..) in CSS doesn't use quotes. Yes they can, optionally.

Thanks to Álvaro G. Vicario. Though he didn't gave an exact answer, the direction was right.
I've checked the page with w3c validator, and found that my Image objects were missing src attribute. Thus, adding img.src = "chrome://{appname}/content/dbutton.png"; helped.
Still, I'm not sure, why the original code author used background style instead of src... Perhaps, that would remain a mystery. :)

Related

Remove all elements from website except X

I'm not really familiar with Javascript, and even less with how Javascript works in Chrome's F12 developer tools. What I'm trying to do is have a favorite which, when clicked on, loads a web page but removes some of the clutter of the page which is loaded (I don't really care if it removes it before the page is loaded, or loads it and then removes it)
For now, I'm trying to figure out how to remove all elements except the one I want to keep (and its' children), namely, one which has the following html:
<div>
<ul class="c-list-news u-relative" data-load-more-content>...</ul>
</div>
I'm trying the following (from what I could find on SO), but I can't find the right selector (or I'm doing something else wrong, not quite sure):
var elem = document.querySelectorAll('body *:not(div ul.c-list-news, div ul.c-list-news *)');
for(var i=0;i<elem.length;i++) {
elem[i].parentElement.removeChild(elem[i]);
}
(PS : I haven't yet looked into how to put it into a favorite/extension, it will come later)
It's probably easier than you realize. :-) You can get the first element matching .c-list-news like this:
const cListNews = document.querySelector(".c-list-news");
If you want to keep its parent, just add .parentNode to that:
const divContainer = document.querySelector(".c-list-news").parentNode;
Then, wipe out body entirely:
document.body.innerHTML = "";
...and put the element back:
document.body.appendChild(cListNews); // Or `divContainer`
I'm not sure I'd expect the page to continue to be readable, though, since of course this completely changes where the element is in the DOM, which may well make the CSS fail.
You can't make a bookmark (favorite) that both loads the page and does this in one go, because javascript: bookmarks work within the context of the current page. You could use something like TamperMonkey which is an extension that lets you run a script automatically when you go to matching URLs.
But you can make a bookmark that you use when you're already on the page: Just use the javascript: pseudo-protocol and follow it with JavaScript code. For instance:
javascript:var divContainer %3D document.querySelector(".c-list-news").parentNode%3Bdocument.body.innerHTML %3D ""%3Bdocument.body.appendChild(divContainer)%3Bconsole.log("done")%3B
I created that by simply removing line breaks from the code (optional), running the code through encodeURIComponent, and putting javascript: on the front. (Some folks would also convert spaces to %20.)
Save the element to keep to a variable. Remove all nodes from the body, or the element that you want, and add the element to keep. Example:
let elementToKeep = document.getElementById('side');
const myNode = document.getElementsByTagName("body")[0];
while (myNode.firstChild) {
myNode.removeChild(myNode.firstChild);
}
myNode.appendChild(elementToKeep);
Using the removeChild method is faster that setting the innerHtml as empty string.
Check here: Remove all child elements of a DOM node in JavaScript

Avoid jquery append closing tag

How can I ensure that this:
$('.graph svg').append('<polyline points="' + '3,' +point+ ' 97,' +point+ '"style="fill:none;stroke:#FFFFFF;stroke-width:0.5"/>');
Actually results in a self closing tag ..../>
Rather than closing with </polyline>
For some reason only the former renders on iOS.
It doesn't result in any tags at all; it results in elements in the DOM. Tags are textual means of describing elements. When you give that string to jQuery, it asks the browser to parse that string and create elements (objects) in memory. The only tags involved are the ones you give to jQuery.
From your update (comment):
...is there another way of doing this that avoids the append method? Here's a fiddle that refuses to work on iOS http://jsfiddle.net/rCfrF/23
That doesn't work for me on Chrome, Firefox, or IE either. I don't think you can add to SVG elements like that, I think jQuery tries to create an HTML element polyline rather than the SVG polyline (which is namespaced).
This works on Chrome, Firefox, IE, and my iPhone 5: Updated version of your fiddle on JSBin (jsFiddle doesn't work properly on my iPhone 5)
function clickappend() {
var svg = $("#graph svg")[0];
var polyline = svg.ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'polyline');
polyline.setAttribute("points", "20,0 20,100");
polyline.style.fill = "none";
polyline.style.stroke = "#232323";
polyline.style.strokeWidth = "0.5";
svg.appendChild(polyline);
alert('ran');
}
You could use:
$('.graph svg').html($('.graph svg').html() + '<polyline points="' + '3,' +point+ ' 97,' +point+ '"style="fill:none;stroke:#FFFFFF;stroke-width:0.5"/>');
This is the inspector's problem. There is a substantial difference between void elements (aka self-closing elements) and others, in that they cannot accept descendant nodes. polyline is such a void element. The inspector may show it as having a closing tag, but it shouldn't be able to accept methods such as – if you tried to insert content between its opening and closing tags that content would likely be inserted after it in the DOM.

IE10 inserting blank text DOM entries

I have a piece of Javascript code that locates the single table in the DOM then tries to manipulate its first child, the thead (actually, it iterates though the children of that child, the tr entries but that's not important to the question). The code to do this is:
var tableNode = document.getElementById("table").firstChild;
This works fine in Firefox ESR (10/17/24) and IE9 but fails in IE10, and the reason appears to be because IE10 is inserting weird DOM entries and it's one of those I'm picking up with firstChild instead of the desired thead. I base this on the DOM dump below along with the fact that tableNode.data is set to the string type.
The DOM in IE10 compatibility mode (where it also works) looks like this:
and you can see that the DOM indeed looks sensible. However, examining the DOM in normal IE10 mode shows this:
For what it's worth, Chrome gives me:
and FF17esr gives me:
neither of which seem to have the empty text elements.
Now, I can run the site in compatibility mode but that's an annoying thing to have to tell all our customers. I can also apparently add the hideous:
<meta http-equiv="X-UA-Compatible" content="IE=9">
to my output but I'm not entirely certain what other effects that may have.
I'd like to understand first why IE10 is adding these nodes whereas IE9/FF/IE10compat aren't. There are some discussions I've found stating that whitespace in the HTML may be causing it but it seems to me that this shouldn't result in random nodes being created and, in any case, I don't think I have any superfluous white space. Although I should mention that the value of tableNode.data mentioned above as type string is actually \n, meaning that the newline at the end of the line may be creating this DOM entry.
But, to be honest, that seems ludicrous. HTML is supposed to ignore whitespace outside of tags as far as I'm aware, or at least fold it into a single element. I find it hard to believe that something like:
<tag>line 1</tag>
<tag>line 2</tag>
would result in three DOM entries, tag, empty node and tag just because there's a newline between them.
Any ideas on how to best solve this? Am I going to have to modify my Javascript code to skip these DOM entries?
You can never know where a browser might insert text nodes so you have to make sure you're getting the first child "element"in case the browser put a text node there.
Here's a simple function that will do that:
getFirstChildElement(parent) {
var node = parent.firstChild;
// advance until we get to an element node (skipping text and comment nodes)
while (node && node.nodeType !== 1) {
node = node.nextSibling;
}
return node;
}
Or, if you just want to get the <thead> element, you can simply use this:
table.getElementsByTagName("thead")[0]
Are you absolutely sure Firefox doesn't show those empty text nodes? I'm asking because it should, if it doesn't then it's a bug in Firefox.
Previously only IE behaved the way you expected. All other browsers including Firefox, Chrome, Safari and Opera followed W3C DOM standards which requires them to retain those whitespace. IE10 now join the ranks of other web browsers and behave in a standards compliant way.
You'd be right to complain that this doesn't make sense but it's what the standards require.
As such, the correct way to get the element is to check it's tagName:
var table = document.getElementById("table");
var child = table.firstChild;
while (child && child.tagName != 'thread') {
child = child.nextSibling;
}
// remember to check child after this point because it may be undefined
Additional explanation
Firebug and Chrome's DOM explorer hides those text elements as a matter of convenience, but it's still there. You can try this out:
<html>
<body>
<div id="foo">
<div id="bar">
</div>
</div>
<script>
var f = document.getElementById('foo');
document.body.innerHTML += f.firstChild.id + <br>;
document.body.innerHTML += f.firstChild.nextSibling.id + <br>;
</script>
</body>
</html>
In all browsers except older versions of IE the above page would output:
undefined
bar
That's because the firstChild is the empty text node. You can console.log it if you like to check out that firstChild.

Measuring the string width in Javascript

I'm trying to precisely fit a string into a certain width. This means the font-size changes depending on the string. I now use the following function, using jQuery:
function fontResize ( )
{
for (var i = $("#date").css("font-size").slice(0, -2); $("#date").width() < $("#clock").width(); i++)
$("#date").css("font-size", i.toString() + "px");
}
The idea is that I set the font-size in the CSS to the lowest possible value. I initialize the loop with this value and increment the font-size with 1 until the string is wider than the width of the containing element. The string in this case is "date" and the containing element is "clock".
Although this works, I think the main disadvantage is that the string has to be first drawn before the width can be determined. This means I cannot call this function before loading the body.
If anyone knows a better way to do this please let me know! Thanks.
To make sure you're getting all the styles and such applied to it that will be applied when the page is fully rendered, yes, you do want to put the element in the DOM (and in the right place in the DOM) before you do your measurement stuff. But you don't have to wait until everything else is there (unless you think it will affect the styling of the string you're measuring). You can put your code in a script block immediately after the element in question — no waiting for ready. The date element will be there and accessible, according to Google's Closure library engineers. E.g., if date is a span:
<body>
...
<span id="date">December 13th</span>
<script>fontResize();</script>
...
...
</body>
It's unfortunate to intermix code and markup like that (particularly if you have separate teams doing the markup and the code), but if your requirement is to size the text absolutely as soon as possible, that's how.
The above also assumes your fontResize function is already loaded (e.g., in a script block higher on the page). This is another reason it's unfortunate to mix markup and code like this, because normally of course you want to put your scripts at the bottom, just before closing the body tag.
However: It may be worth experimenting to see if you can do your resizing in the script you put just before the closing body tag instead. There'd be a small window of opportunity for the page not to look right, but quite small and of course pages tend to look a little funny as they load anyway. Then you wouldn't have the intermixing problem and wouldn't have to load your scripts early. You may find that the just-before-the-closing-body-tag is soon enough.
How about using the canvas, and the measureText method?
$(function () {
var canvas = $("canvas")[0];
var context = canvas.getContext("2d");
var text = "hello world";
context.font = "40pt Calibri";
var metrics = context.measureText(text);
alert(metrics.width);
});

Setting style with javascript unexpected results

I am detecting browsers and trying to apply style,
alert(BrowserDetect.browser);
if(BrowserDetect.browser == "Opera") {
document.getElementById(myBox).style.paddingTop = "5px";
}
Alert Shows the browsers name accurately but why is the style not getting applied?
Try adding an alert inside the if and see if it executes. Chances are, there's whitespace around the Operastring.
Put two copies of the alert() call inside the if statement, one before the style assignment and one after. I think you'll find that the second doesn't happen, for any of several reasons:
The variable myBox doesn't exist
myBox has a value that is not an ID of a valid DOM element
myBox does name a valid element, but it is not a block type element, so padding doesn't apply
You can also try running the same code in another browser with a solid JS debugger, like Firefox + Firebug or Safari with the Develop tools enabled. (Preferences | Advanced.) This may lead you to the problem with that style assignment faster.

Categories

Resources