2

I am new to javascript. I have a table of content which I want to rearrange its row and column based on user's window size using window.onresize.

window.onresize = function () {

    var w = window.innerWidth;
    var nocolumn = Math.floor(w / 252);
    if (nocolumn == 0) {
        nocolumn = 1;
    }

    var table = document.getElementById("MainContent_DataList1");
    var tbody = table.getElementsByTagName("tbody")[0];
    var link = tbody.getElementsByTagName("a");

    var norow = Math.ceil(link.length / nocolumn);
    tbody.innerHTML = "";

    console.log(norow + " " + link.length + " " + nocolumn);
    for (var i = 0; i < norow; i++) {
        var row = document.createElement("tr");
        tbody.appendChild(row);
        for (var j = 0; j < nocolumn; j++) {
            var cell = document.createElement("td");
            row.appendChild(cell);
            if ((i * nocolumn + j) < link.length) {
                cell.appendChild(link[i * nocolumn + j]);
            }
        }
    }
};

I dont understand why the variable "link" array becomes empty after I use innerHTML = ""; but I stored it before its cleared. Is it somewhere I did wrongly or there are other ways to do this?

Antony
  • 14,900
  • 10
  • 46
  • 74
Ren Fa Kay
  • 107
  • 1
  • 9

2 Answers2

1

When you delete the innerHTML you delete the DOM objects thus every reference to them will point to null.

A work around it will be to clone these objects:

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = {};
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

window.onresize = function () {

    var w = window.innerWidth;
    var nocolumn = Math.floor(w / 252);
    if (nocolumn == 0) {
        nocolumn = 1;
    }

    var table = document.getElementById("MainContent_DataList1");
    var tbody = table.getElementsByTagName("tbody")[0];
    var tmp = tbody.getElementsByTagName("a");
    var link = clone(tmp);

    var norow = Math.ceil(link.length / nocolumn);
    tbody.innerHTML = "";

    ...
}

Credit for the clone() method: https://stackoverflow.com/a/728694/1057429

Community
  • 1
  • 1
Nir Alfasi
  • 53,191
  • 11
  • 86
  • 129
  • Far simpler (and safer) to iterate over the NodeList by index and assign to an array (i.e. convert the NodeList to an array). – RobG Jun 17 '13 at 02:58
  • `When you delete the innerHTML you delete the DOM objects thus every reference to them will point to null.` is incorrect. They are removed from the NodeList created by `getElementsByTagName`, other references not involving a live NodeList are unaffected (such as assigning references from an array or using [querySelectorAll](http://www.w3.org/TR/selectors-api/#queryselectorall), which creates static NodeLists). – RobG Jun 17 '13 at 03:00
  • @RobG maybe I'm missing something but if you'll try to debug his code, when you'll get to `tbody.innerHTML = "";` you can inspect the value of `link` and see that it becomes `null` – Nir Alfasi Jun 17 '13 at 03:03
  • @RobG and about your first comment - I totally agree with you, for this specific case it'll be enough to convert the NodeList to an array, but I was aiming to provide a solution that will be more generic and can be in other cases of DOM elements as "an input" as well. – Nir Alfasi Jun 17 '13 at 03:08
  • 1
    wow thanks all the quick answers! i didn't know i could clone these objects. i got it working already! – Ren Fa Kay Jun 17 '13 at 03:13
  • @alfasin—the important bit is "every reference". Only those that are formed by live NodeLists will be "null" (actually they'll be undefined, but whatever). Other references from say *getElementById* or after conversion to an array are fine. – RobG Jun 17 '13 at 03:36
  • Also "cloning" host objects as in the answer is not very robust. The result of the typeof operator is only specified for native objects, not for host ubjects which can return any value, or even throw an error. Nor are the properties necessarily enumerable, and you may bet more properties than you expect. Far better to iterate over the object using specified behaviour to create an equivalent array. – RobG Jun 17 '13 at 03:40
  • @RobG I'm still not sure I understand (please bear with me): if you copy the values in the NodeList to an array - of course the values will be there, but if you'll try to get the values from that NodeList again after assigning `tbody.innerHTML = "";` using `getElementById` it won't work. – Nir Alfasi Jun 17 '13 at 03:43
  • @alfasin—the NodeList returned by *getElementsByTagName* is live, so any change to the DOM also updates the NodeList, as if you called *getElementsByTagName* again. There are many such lists or collections, e.g. table.rows, select.options, document.images, document.links, etc. – RobG Jun 17 '13 at 05:09
0

As other answers have suggested, getElementsByTagName returns a live NodeList. Therefore, when you delete all the elements from the body, the NodeList is updated to contain no nodes.

As an alternative, you can use querySelectorAll, which returns a static NodeList, or use getElementsByTagName and assign references to an array before clearing the nodes from the body, e.g.

function getNodes() {
  var tbody = document.body || document.getElementsByTagName('body')[0];
  var nodes, link;

  if (tbody.querySelectorAll) {
    link = tbody.querySelectorAll('*');
  } else {
    nodes = tbody.getElementsByTagName("*");
    link = [];

    for (var i=0, iLen=nodes.length; i<iLen; i++) {
      link[i] = nodes[i];
    }
  }
  return link;
}
RobG
  • 142,382
  • 31
  • 172
  • 209