How to characterize z-indexing for the DOM? (2) - javascript

I've been a bit care-less with choosing z-indexes in my CSS.
I'd like to traverse the DOM and report all the z-indexes with their respective ID's.
For example:
id z-index
header 10
main 0
menu 20
The end result would be an object whose keys are the element id and the value is the z-index. One might call it z_obj
// pseudo code
var z_obj = {el_id: el_zindex};
Getting the z-index for each element ( el ) should be easy using something like filter and the code below:
// attr is attribute
data = _.filter(el.attributes, function (attr) {
return (/^z-index/).test(atttr.name);
});
But how would I traverse the DOM to get all z-indexes and store that in an object?
I'd like to do this w/ out libraries, and using the code above if possible.
This would be a good debug tool in general.

You can get all elements with getElementsByTagName("*"), iterate over the collection with a for loop, and use window.getComputedStyle(Node). From there, you can retrieve the z-index. Here's an example:
var zObj, allEls, i, j, cur, style, zIndex;
zObj = {};
allEls = document.body.getElementsByTagName("*");
for (i = 0, j = allEls.length; i < j; i++) {
cur = allEls[i];
style = getComputedStyle(cur);
zIndex = style.getPropertyValue("z-index");
zObj[cur.id] = zIndex;
}
DEMO: http://jsfiddle.net/mj3cR/1/
Where zObj is an Object, keys represented by the id attributes, and values represented by the z-index style.
Note that getComputedStyle is not supported in IE < 9, but of course there are many polyfills :)

reportZ = function () {
var z_arr = {},
all_el = document.body.getElementsByTagName("*"),
i,
len,
cur,
style,
z_index;
for (i = 0, len = all_el.length; i < len; i++) {
cur = all_el[i];
style = win.getComputedStyle(cur);
z_index = style.getPropertyValue("z-index");
if (z_index !== "auto") {
z_arr[i] = [cur.id, cur.tagName, cur.className, z_index];
}
}
return z_arr;
};

Related

TypeError: ...[i] is undefined

What is wrong at this part of script:
function refreshLabels() {
// loop through all document elements
var allnodes = document.body.getElementsByTagName("*");
for (var i=0, max=allnodes.length; i < max; i++) {
// get id current elements
var idname = allnodes[i].id;
// if id exists, set get id current elements
if (idname !== '') {
allnodes[i].textContent = multilang.get(idname);
}
}
}
Firebug always say:
"TypeError: allnodes[i] is undefined"
I can't find any solution to fix this - is this part wrong?
"for (var i=0, max=allnodes.length; i < max; i++)"
Btw., also Chrome say it - so its not just a FF problem.
Your issue with changing the textContent is that you are actually mutating the node child's nodes of the allnodes array and thereby also mutating the length of the allnodes array, reducing its size so that the loop continues running past the end of the newly shortened array.
Refer to MDN web docs for what textContent is doing:
function refreshLabels() {
// loop through all document elements
var allnodes = document.body.getElementsByTagName("*");
console.log(allnodes.length)
for (var i = 0, max = allnodes.length; i < max; i++) {
// get id current elements
var idname = allnodes[i].id;
// if id exists, set get id current elements
if (idname !== '') {
allnodes[i].textContent = idname;
console.log(allnodes.length) // this length will change
}
}
}

Get Z-index of each element on the page

I am trying to find the highest z-index on a page. I am using this
var getStyleProp = function(el, prop){
return window.getComputedStyle(el, null).getPropertyValue(prop);
}
var getHighestZIndex = function(){
var highestZIndex = 0,
HTMLElems = ["a","abbr","acronym","address","applet","area","article","audio","b","base","basefont","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","embed","fieldset","figcaption","figure","footer","form","frame","frameset","h1, h2, h3, h4, h5, h6","head","header","hgroup","hr","html","i","iframe","img","input","ins","isindex","kbd","keygen","label","legend","li","link","listing","main","map","mark","menu","menuitem","meta","meter","nav","noembed","noscript","object","ol","optgroup","option","output","p","param","plaintext","pre","progress","q","rp","rt","rtc","ruby","s","samp","script","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","title","tr","track","tt","u","ul","var","video","wbr","xmp"],
tags,
zIndex;
for (var i = 0; i < HTMLElems.length; i++){
tags = document.getElementsByTagName(HTMLElems[i]);
if (tags){
for (var c = 0; c < tags.length; c++){
zIndex =getStyleProp(tags[c], "z-index");
console.log(tags[c], "zIndex=", zIndex);
if (zIndex > highestZIndex){
highestZIndex = zIndex;
}
}
}
}
return highestZIndex;
}
console.log(getHighestZIndex());
But everything is coming back as "auto". This ancient article explains how a "bug" is causing this behavior. I've tried to make clones of each node, set the position to relative, and then get the z-index,
cl.style.display = "none";
cl.style.position = "absolute";
zIndex = (getStyleProp(cl, "z-index"));
but that is not working either. What is wrong here? Is there a cheaper way to do this than recreating everything on the page?
JSBIN
The node's clone does not seem to get the z-index, while the node itself returns the right value. You could try using it instead (not sure how it might react on a page with lots of content):
var getHighestZIndex = function () {
var highestZIndex = 0,
zIndex,
pos,
tags = document.body.getElementsByTagName('*');
for (var c = 0; c < tags.length; c++) {
// Get the original 'position' property
pos = getComputedStyle(tags[c]).position;
// Set it temporarily to 'relative'
tags[c].style.position = "relative";
// Grab the z-index
zIndex = getComputedStyle(tags[c]).zIndex;
// Reset the 'position'
tags[c].style.position = pos;
console.log(tags[c], "zIndex=", zIndex);
if (zIndex > highestZIndex) { highestZIndex = zIndex; }
}
return highestZIndex;
};
console.log(getHighestZIndex());
JS Fiddle Demo
Changing the element's position temporarily might produce a glitch. You'll need to test that on a page with lots of contents and elements that are position:absolute; or position:fixed;.
If this doesn't fit your use-case, just let me know, and I'll remove it. However, as a thought.
Can you loop through all the tags, and if the value is "auto" assume it's 999. If it's >= 1000, take that as your "highest" value. Then, increment your zIndex up from your highest number that way. This way, the first tag you place will be 1000, the next will be 1001.
var elements = document.getElementsByTagName("*"),
maxZIndex = 999;
for( var i=0; i < elements.length; i++ ) {
var zIndex = parseInt(elements[0].style.zIndex) || 999;
maxZIndex = zIndex > maxZIndex ? zIndex : maxZIndex;
}
return maxZIndex;
This would fit a use case where you are just trying to make sure that the tag you are placing is greater than anything on the page...such as placing a modal.
999 is overkill...somewhat of a "just in case" I missed something because anything with z-index:auto is equivalent to zero. See the following "proof" where even though my z-index is only "1" it overlaps boxes that are 3-deep of "auto".
<div style='position:absolute;background-color:white;z-index:1;width:94px;height:94px;'>
</div>
<div style='position:absolute;background-color:red;width:100px;height:100px;'>
<div style='position:absolute;background-color:blue;width:98px;height:98px;'>
<div style='position:absolute;background-color:green;width:96px;height:96px;'>
</div>
</div>
</div>

Javascript - efficiently insert multiple HTML elements

I'd like to create a select element with a list of a user's Facebook friends (obtained as a JSON object). I hardcode <select id="friends"></select> into my HTML, then use the following Javascript code to parse the JSON and insert each friend as an option of the select element:
var msgContainer = document.createDocumentFragment();
for (var i = 0; i < response.data.length; i++) {
msgContainer.appendChild(document.createTextNode('<option value="'+response.data[i].id+'">'+response.data[i].name+'</option>'));
}
document.getElementById("friends").appendChild(msgContainer);
This almost works, except that it inserts < and > instead of < and >. How can I fix it, and is there a more efficient way to insert multiple HTML elements using pure Javascript (not JQuery)?
Not sure why you're creating a text node, but it would seem that you want to create option elements, so you could use the Option constructor instead.
var msgContainer = document.createDocumentFragment();
for (var i = 0; i < response.data.length; i++) {
msgContainer.appendChild(new Option(response.data[i].name, response.data[i].id));
}
document.getElementById("friends").appendChild(msgContainer);
Or you can use the generic document.createElement().
var msgContainer = document.createDocumentFragment();
for (var i = 0; i < response.data.length; i++) {
var option = msgContainer.appendChild(document.createElement("option"));
option.text = response.data[i].name;
option.value = response.data[i].id;
}
document.getElementById("friends").appendChild(msgContainer);
It's nice to have a helper function for creating elements and setting properties at the same time.
Here's a simple example of one:
function create(name, props) {
var el = document.createElement(name);
for (var p in props)
el[p] = props[p];
return el;
}
It can be expanded to cover some specific needs, but this will work for most cases.
You'd use it like this:
var msgContainer = document.createDocumentFragment();
for (var i = 0; i < response.data.length; i++) {
msgContainer.appendChild(create("option", {
text: response.data[i].name,
value: response.data[i].id
}));
}
document.getElementById("friends").appendChild(msgContainer);
Try this in your for loop instead:
var o = document.createElement('option');
o.setAttribute('value', response.data[i].id);
o.appendChild(document.createTextNode(response.data[i].name));
msgContainer.appendChild(o);
For those who need similar functionality, you can generate an html snippet using template literals and insert it using innerHTML property. Plus you can set attributes and selected while iterating over the items:
const el = document.createElement('select');
el.innerHTML = ['John', 'Sally', 'Betty'].reduce((acc, prev, i) => {
if (i === 1) {
return acc + `<option selected>${prev}</option>`;
}
return acc + `<option>${prev}</option>`;
}, '');
const root = document.querySelector('#app');
root.appendChild(el);
In modern browsers this is faster than creating elements one by one imperatively.

How would I generate a list of distinct HTML tags in a page using JavaScript (must work in IE6)

The end result I'm after is a JavaScript array containing a list of tag names that are used in the HTML document eg:
div, span, section, h1, h2, p, etc...
I want the list to be distinct and I'm not interested in tags within the <head> of the document (but they can be there if it's a performance hog to exclude them).
This has to work in IE 6, 7, & 8 and I don't want to use jquery.
What would be the most efficient way of doing this?
What you're looking for is document.all.tagName
At the top of my head, a for loop like this should do it (providing that you're gonna filter the tags you don't want on that list)
for(i = 0; i < document.all.length; i++)
{
console.log(document.all[i].tagName);
}
Here is a cross-browser solution:
var tags = {}; // maintains object of tags on the page and their count
var recurse = function(el) {
// if element node
if(el.nodeType == 1) {
if(!tags[el.tagName])
tags[el.tagName] = 0;
tags[el.tagName]++;
}
// recurse through the children
for(var i = 0, children = el.childNodes, len = children.length; i < len; i++) {
recurse(children[i]);
}
}
recurse(document);
// if you want just what's in the body(included)
var bodies = document.getElementsByTagName("body");
for(var i = 0; i < bodies.length; i++)
recurse(bodies[i]);
To get a unique list of tagnames in a document as an array that works in all browsers back to IE 6 and equivalent:
function listTagNames() {
var el, els = document.body.getElementsByTagName('*');
var tagCache = {};
var tagname, tagnames = [];
for (var i=0, iLen=els.length; i<iLen; i++) {
tagname = els[i].tagName.toLowerCase();
if ( !(tagname in tagCache) ) {
tagCache[tagname] = tagname;
tagnames.push(tagname);
}
}
return tagnames;
}
If you think there might be an inherited object property that is the same as a tag name, use a propertyIsEnumerable test:
if (!tagCache.propertyIsEnumerable(tagname)) {
so it will work even in Safari < 2.
Get all tagnames in the document, unique, crossbrowser, plain js:
var els = document.getElementsByTagName('*'), tags = [], tmp = {}
for (var i=0;i<els.length;i+=1){
if (!(els[i].tagName in tmp)){
tags.push(els[i].tagName);
tmp[els[i].tagName] = 1;
}
}
use
if (!(els[i].tagName in tmp)
&& !/head/i.test(els[i].parentNode.tagName)
&& !/html|head/i.test(els[i].tagName))
to exclude the <head>

How to create array and divs with a unique height using jQuery?

I need 100 small divs for my chart, every time I generate them, I they all appear as the same height; the last value from the array.
var valuesG = new Array(100);
for (i = 0; i < valuesG.length; i++ ) {
valuesG[i] = Math.floor(Math.random() * 101);
$("#second").append("<div class='single'></div>");
$(".single").css('height', valuesG[i])
}
Any ideas why this is happening?
You are applying the new height to ALL the .single elements in every iteration. In the last iteration, they end up having the same height.
You could do it like this:
$('<div class="single">')
.css('height', valuesG[i])
.appendTo($('#second'));
Also, your code is not very efficient, take a look at this:
var valuesG = [], //array literal
$elements = $(); //empty jQuery object
for (var i = 0; i < 100; i++) { //we don't have to query array length each iteration
valuesG[i] = Math.floor(Math.random()*101);
//collect the elements into a jQuery object
$elements = $elements.add($('<div class="single">').css('height', valuesG[i]));
}
$elements.appendTo($("#second")); //insert to DOM once - much quicker
jsFiddle Demo
Currently, you're selecting all elements with class single. To get the desired effect, use the appendTo method in the way as shown below.
Side note, the generated heights are not unique, but random. It's possible that two elements exist with, say, height 50. See this question for a method to generate unique random numbers.
var valuesG = new Array(100), i;
for ( i=0; i < valuesG.length; i++ )
{
valuesG[i] = Math.floor(Math.random()*101);
$("<div class='single'></div>")
.css( 'height', valuesG[i] )
.appendTo("#second");
}
You get only one height(last one) because you set same css class in all div and update it's height in every loop so last height value will be applied to all.
As a solution try this:
for ( i=0; i < valuesG.length; i++ ) {
valuesG[i] = Math.floor(Math.random()*101);
$("<div class='single'></div>").css('height',valuesG[i]).appendTo("#second");
}

Categories