views:

109

answers:

2

I have a Django admin page for a nested category list like this: alt text

I wrote this script to sort the list and present it hierarchically:

{% extends "admin/change_list.html" %}
{% load i18n %}
{% block footer %}

<script>
(function(){

  var rows=document.getElementById('result_list').getElementsByTagName('tr'),
      table=rows[1].parentNode||rows[1].parentElement,
      i=0, r,      // skip the first row
      data={};     // store category data

  while (r=rows[++i]) {
    var catName=r.getElementsByTagName('a')[0],
        k=catName.innerHTML,
        opts=r.getElementsByTagName('select')[0],
        j=-1, opt;
    while (opt=opts[++j]) {
      if (!opt.selected) continue;
      data[k] = {  
        title:        k,
        children:     {},
        parentName:   opt.innerHTML, 
        parentId:     opt.value, 
        catName:      catName,
        row:          r
      } 
    }
  }

  for (var sub in data) {
    if (data[sub].parentName == sub) continue;
    for (var sup in data) {
      if (sup == data[sub].parentName) {
        data[sup].children[sub]=data[sub];
        data[sub].parent = data[sup];
        break;
      }
    }
  }

  var alt = 0;
  for (var leaf in data) {
    if (data[leaf].parentName != leaf) continue;
    walk(data[leaf], leaf, function (node, nodeName) {
      var n=node, t=n.title;
      while (n=n.parent) {
        t = ' &middot; &nbsp;' + t;
      }
      node.catName.innerHTML = t;
      node.row['class']=node.row['className']='row'+alt++%2;
      table.removeChild(node.row);
      table.appendChild(node.row);
    });
  }

  function walk (leaf, leafName, cb) {
    if (cb) cb(leaf, leafName);
    leaf.ready = true;
    for (var kid in leaf.children) {
      if (leaf.children[kid].ready) continue;
      walk(leaf.children[kid], kid, cb);
    }
  }

}());
</script>

{% endblock %}

...the script runs fine and the list looks like this: alt text

My question is: I feel like the script is prone to memory leaks in UAs with weak garbage collection because of the circular references created by the parent / child stuff. Is this something I should be worried about? Is there a better way to write the script? Should I be deleting a bunch of stuff at the end of the script, and if so, what?

+1  A: 

The script doesn't seem to contain any serious memory leaks, as it doesn't leave any functions ( like cb ) around after the walk. So the garbage collector should succesfully collect all the garbage created.

You might however have high memory usage during the execution if the number of iterations are really high.

[ Amatuer article I wrote on this some time back ] http://stefan.artspace44.com/javascript/memory-leaks/

NixNinja
+1  A: 

I see some minor leaks, IE garbage collector has a problem cleaning live node references inside functions. This is because the DOM and JavaScript both have it's own garbage collector and basically, they don't want to pick a fight with eachother over a reference.

Since you call this script once per page? The memory leak is minute and can actually be ignored, unless people open 100+ pages in one session. Cleaning up is nicer though.

{% extends "admin/change_list.html" %}
{% load i18n %}
{% block footer %}

<script>
(function(){

  var rows=document.getElementById('result_list').getElementsByTagName('tr'),
      table={},
      i=0, r,      // skip the first row
      data={};     // store category data

  // table is now a JS object with a el reference to an element.
  table.el = rows[1].parentNode||rows[1].parentElement;

  while (r=rows[++i]) { // you skip the first row, that correct? Else use i++
    var catName=r.getElementsByTagName('a')[0],
        k=catName.innerHTML,
        opts=r.getElementsByTagName('select')[0],
        j=-1, opt;
    while (opt=opts[++j]) {
      if (!opt.selected) continue;
      data[k] = {  
        title:        k,
        children:     {},
        parentName:   opt.innerHTML, 
        parentId:     opt.value, 
        catName:      catName,
        row:          r
      } 
    }
  }
  // nullify node references
  r = catName = opt = rows =  null;

  for (var sub in data) {
    if (data[sub].parentName == sub) continue;
    for (var sup in data) {
      if (sup == data[sub].parentName) {
        data[sup].children[sub]=data[sub];
        data[sub].parent = data[sup];
        break;
      }
    }
  }

  var alt = 0;
  for (var leaf in data) {
    if (data[leaf].parentName != leaf) continue;
    walk(data[leaf], leaf, function (node, nodeName) {
      var n=node, t=n.title;
      while (n=n.parent) {
        t = ' &middot; &nbsp;' + t;
      }
      node.catName.innerHTML = t;
      node.row['class']=node.row['className']='row'+alt++%2;
      // if table wasn't a JS object, this closure would not have been cleaned up.
      // a refence to table is kept, not to a live DOM element.
      table.el.removeChild(node.row);
      table.el.appendChild(node.row);
    });
  }


  function walk (leaf, leafName, cb) {
    if (cb) cb(leaf, leafName);
    leaf.ready = true;
    for (var kid in leaf.children) {
      if (leaf.children[kid].ready) continue;
      walk(leaf.children[kid], kid, cb);
    }
  }

}());
</script>

{% endblock %}

Was a bit confusing to sort out, since your JS object names imply they are DOM elements =P but I think I figured it out correctly. But you might want to go over the code and nullify other DOM elements I might've overlooked (once you're done with them ofcourse).

BGerrissen
Thanks for your response. Question: could `r`, `catName`, `opt`, and `rows` (all the variables you nulled) be made into properties of some object, like table.el, instead of nulling the references to them? I'm curious why you took two different approaches for `table` and for the others.
no
The first four are only used in one loop so don't see why you would want to persist them on an object, the table you reuse in an anonymous function through a closure passed to walk(). The loop where walk is called creates a function each iteration, so that could've been a lot of potential live element references left behind. I stuck it on an object so the function only knows about the JS object reference and the object holds the DOM element reference.
BGerrissen
In fact, you could also declare the function passed to walk() before the loop so you dont create a function each iteration, buts thats a minor optimisation.
BGerrissen
Good point on moving the `cb` function out. BTW I think `r` and `opt` don't need to be nulled; they have to evaluate to false for the loops they are associated with to complete.
no
Yeh yer totally right about r and opt, the loop cleans them up.
BGerrissen