Inserting multiple nodes with Document Fragments

Posted on Nov 14, 2010 in Code, JavaScript | 0 comments

This is something I’ve always had lingering at the back of my mind. As any JS developer knows, changing the contents of the DOM is slow since it causes a reflow (and often a repaint) of the web page. But what if you need to insert multiple nodes into a page? I confess I’ve always simply done it in a loop – which is horrendous from an efficiency point of view.

Resig suggests an alternate approach: using a document fragment to bundle together the nodes, then do a single insert.

Very handy technique.

http://ejohn.org/blog/dom-documentfragments/

Read More

Queuing expensive DOM changes (avoiding timing problems with JS)

Posted on Aug 9, 2010 in Code, JavaScript | 0 comments

This is a re-write of an old post, written back in 2007. I recently re-used this code in another script and it’s as useful today as it was then. Plus I understand it a little better now, so I thought it was time to re-discuss it.


As any client-side web developer knows, DOM scripting is inherently slow. Accessing and manipulating the DOM requires the browser’s javascript engine to interface with the DOM API – which in of itself is slow: any time you have two fundamentally different components talking to one another via an interface you’re going to get a bit of a bottleneck. But also, accessing and especially changing the contents of the DOM can cause reflows (the re-creation of the internal representation of the visible page within the browser) and repaints (the actual re-rendering of the visible web page to the user). And this is slow!

So what of it?

Well, here’s a common scenario. Try dynamically inserting a large block of HTML into your page, then immediately accessing an element within it. Does it work? Well… sometimes it may, sometimes it may not – it can depend on the quality of the browser, how busy the CPU is, the OS, the volume of HTML inserted – a whole range of factors. From a coding standpoint (and this is what *I* find interesting!), it looks fine: on one line you insert the content, on the next you access it. What could be wrong with that? Well, this is one of those baaaad instances where you need to have an understanding of what’s going on within the browser. You shouldn’t, but you do.

So here’s how to get around it. The following code lets you queue your DOM changes. It only proceeds to the next item in the queue when a custom assertion (e.g. “does element X exist?”) is confirmed. This can ensure that regardless of browser, environment, etc. your code will execute in a sequential manner where in every step you can rely on the DOM containing the content that you expect.

The code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var $Q = {
    // each index should be an array with two indexes, both functions:
    // 0: the code to execute
    // 1: boolean test to determine completion
    queue: [],
    run: function()
    {
        if (!$Q.queue.length)
            return;

        // if this code hasn't begun being executed, start 'er up
        if (!$Q.queue[0][2])
        {
            $Q.queue[0][0]();
            $Q.queue[0][2] = window.setInterval("$Q.process()", 50);
        }
    },
    process: function()
    {
        if ($Q.queue[0][1]())
        {
            window.clearInterval($Q.queue[0][2]);
            $Q.queue.shift();
            $Q.run();
        }
    }
}

Usage

It works very simply. Any time you have something that could take up lots of time, just add it to the queue. When you’re ready to run that code, just run() it.

1
2
3
4
5
6
7
8
9
10
11
12
$Q.push([
  function() {
    // do expensive stuff here...
  },
  function () {
    // do our test here and stash it in $boolean
    ...
    return $boolean;
  }
]);

$Q.run();
Read More

Removing preferences from Firefox extension on uninstallation

Posted on Jan 12, 2010 in Code, JavaScript | 0 comments

Apparently Firefox 3.7 will be providing a substantially better way to attach code to key events in an extension’s life (like installation, uninstallation, update etc), but in the meantime we’re stuck with manually attaching our code to observers that detect those events. A little cumbersome, but workable.

Anyway, this is taken from this page on songbirdnest.com. I just made a couple of minor tweaks needed to their code: it looks like they just copied and pasted it from elsewhere, so a couple of vars – Ci and Cc – weren’t defined. The following code works as a standalone component.

Read their page for some caveats and other comments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
var myUninstallObserver = {
 _uninstall : false,
 _tabs : null,
 Ci: Components.interfaces,
 Cc: Components.classes,

 observe : function(subject, topic, data) {
 if (topic == "em-action-requested") {
 // extension has been flagged to be uninstalled
 subject.QueryInterface(Ci.nsIUpdateItem);
 if (subject.id == "[email protected]") {
 if (data == "item-uninstalled") {
 this._uninstall = true;
 } else if (data == "item-cancel-action") {
 this._uninstall = false;
 }
 }
 } else if (topic == "quit-application-granted") {
 // we're shutting down, so check to see if we were flagged for uninstall - if we were, then cleanup here
 if (this._uninstall) {
 // code here!
 }
 this.unregister();
 }
 },

 register: function() {
 var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
 observerService.addObserver(this, "em-action-requested", false);
 observerService.addObserver(this, "quit-application-granted", false);
 },
 
 unregister : function() {
 var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
 observerService.removeObserver(this, "em-action-requested");
 observerService.removeObserver(this, "quit-application-granted");
 }
}

myUninstallObserver.register();
Read More

XUL: innerHTML()

Posted on Jul 16, 2009 in Code, JavaScript, XUL | 0 comments

While I’m on a XUL bint… here’s how to implement innerHTML in XUL. Invaluable for debugging:

1
2
3
4
Object.prototype.xulInnerHTML = function()
{
  return (new XMLSerializer()).serializeToString(this);
};

Usage:

1
2
var myElement = document.getElementById("myID");
alert(myElement.xulInnerHTML);

EDIT: I’ve since learned that extending the built-into JS objects in this manner is NOT recommended. Instead you should implement this function as a procedural function instead. Just a heads up.

Read More

Deleting selected row(s) from XUL tree

Posted on Jul 16, 2009 in Code, JavaScript, XUL | 0 comments

XUL is fun, but DAMN there’s not enough doc out there. Coming up with this function took far longer than it should! Other functions I found on the web didn’t work for my case.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var g = {};
g.removeSelectedTreeRows = function(treeID)
{
 var tree = document.getElementById(treeID);
 var rangeCount = tree.view.selection.getRangeCount();
 var start = {};
 var end   = {};
 for (var i=0; i<rangeCount; i++)  
 {  
 tree.view.selection.getRangeAt(i, start, end);
 for (var c=end.value; c>=start.value; c--)  
 {
 tree.view.getItemAtIndex(c).parentNode.removeChild(tree.view.getItemAtIndex(c));
 }
 }
};
Read More

Function: g.get_ancestor_node() (requires Prototype)

Posted on Jun 6, 2008 in Code, JavaScript | 2 comments

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var g = {}; // our namespace

/**
 * Helper function to return the first ancestor node of a particular type. If the node isn't found, it
 * returns null.
 *
 * @param object a Prototype extended node
 * @param string target_node the target node (e.g. "TR")
 */

g.get_ancestor_node = function(el, target_node)
{
  target_node = target_node.toUpperCase();

  var found_el = null;
  while (el.ancestors().length)
  {
    if (el.nodeName == target_node)
    {
      found_el = el;
      break;
    }
    el = el.ancestors()[0];
  }
 
  return found_el;
}
Read More

Function: g.toggle_unique_class (requires Prototype)

Posted on Jun 6, 2008 in Code, JavaScript | 0 comments

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var g = {}; // namespace

/**
 * Helper function to toggle between any number of classes that can be applied to a single element
 * at one time. The idea is that often an element needs a different class at a different time, e.g.
 * "red", "blue", green" but cannot have more than one at once. This function ensures it's correct.
 *
 * @param object el a Prototype extended node
 * @param string class the class name to apply
 * @param array all_classes. All class that may
 */

g.toggle_unique_class = function(el, class, all_classes)
{
  for (var i=0; i<all_classes; i++)
  {
    if (el.hasClassName(all_classes[i]))
      el.removeClassName(all_classes[i]);
  }
 
  el.addClassName(class);
}
Read More

jQuery plugin: switchClass

Posted on Apr 18, 2008 in JavaScript | 0 comments

I had a little JS problem which I thought I’d solve as a plugin.

This plugin provides functionality similar to jQuery’s built in toggleClass() function, but instead of adding/removing a single class from an element, switchClass toggles between TWO classes ensuring that one and only one of the two classes is assigned to an element (or elements) at one time. It leaves all other element classes untouched and only affects the two classes specified.

See demo and download script here.

Read More

Function: Array.customJoin()

Posted on Jan 28, 2008 in Code, JavaScript | 0 comments

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
 * Simple function that acts like String.join(), except it allows for a custom final joiner. This
 * is helpful in situations where you want to join an array in human readable form, e.g.
 *   var arr = ["one", "two", "three"];
 *   alert(arr.customJoin(", ", " and ")); // alerts "one, two and three"
 */

Array.prototype.customJoin = function(glue, finalGlue)
{
  // if there's at least one element, pop off the LAST item
  var lastItem = null;
  if (this.length > 0)
    lastItem = this.pop();

  // now, if there's still 1 or more items left, join them with the glue
  if (this.length > 0)
  {
    var str = this.join(glue);
    str += finalGlue + lastItem;
  }
  // otherwise there was only one item: just return the string
  else
    str = lastItem;

  return str;
}
Read More