Index: django/contrib/admin/media/js/SelectBox.js
===================================================================
--- django/contrib/admin/media/js/SelectBox.js	(revision 14788)
+++ django/contrib/admin/media/js/SelectBox.js	(working copy)
@@ -1,22 +1,35 @@
 var SelectBox = {
     cache: new Object(),
     init: function(id) {
-        var box = document.getElementById(id);
-        var node;
-        SelectBox.cache[id] = new Array();
-        var cache = SelectBox.cache[id];
-        for (var i = 0; (node = box.options[i]); i++) {
-            cache.push({value: node.value, text: node.text, displayed: 1});
-        }
+        var cache = [];
+        var nogroup = [];
+        django.jQuery("#" + id + " > option").each(function() {
+            nogroup.push({value: this.value, text: this.text, displayed: 1});
+        });
+        cache.push({'group': null, 'items': nogroup});
+
+        django.jQuery("#" + id + " optgroup").each(function() {
+            var group = [];
+            django.jQuery("option", this).each(function() {
+                group.push({value: this.value, text: this.text, displayed: 1});
+            })
+            cache.push({'group': django.jQuery(this).attr('label'), 'items': group});
+        });
+        SelectBox.cache[id] = cache;
+        SelectBox.sort(id, false);
     },
     redisplay: function(id) {
         // Repopulate HTML select box from cache
-        var box = document.getElementById(id);
-        box.options.length = 0; // clear all options
-        for (var i = 0, j = SelectBox.cache[id].length; i < j; i++) {
-            var node = SelectBox.cache[id][i];
-            if (node.displayed) {
-                box.options[box.options.length] = new Option(node.text, node.value, false, false);
+        var ctr = django.jQuery("#" + id).empty(), ct;
+        var cache = SelectBox.cache[id];
+        for(var i = 0; i < cache.length; i ++) {
+            var gr = cache[i];
+            if(gr.items.length == 0) continue; //skip empty groups
+            if(gr.group == null) ct = ctr;
+            else ct = django.jQuery("<optgroup>").attr('label', gr.group).appendTo(ctr);
+            for(var j = 0; j < gr.items.length; j++) {
+                var itm = gr.items[j];
+                if(itm.displayed) django.jQuery("<option>").attr('value', itm.value).text(itm.text).appendTo(ct);
             }
         }
     },
@@ -24,88 +37,83 @@
         // Redisplay the HTML select box, displaying only the choices containing ALL
         // the words in text. (It's an AND search.)
         var tokens = text.toLowerCase().split(/\s+/);
-        var node, token;
-        for (var i = 0; (node = SelectBox.cache[id][i]); i++) {
-            node.displayed = 1;
-            for (var j = 0; (token = tokens[j]); j++) {
-                if (node.text.toLowerCase().indexOf(token) == -1) {
-                    node.displayed = 0;
+        var cache = SelectBox.cache[id];
+        for(var i = 0; i < cache.length; i ++) {
+            var gr = cache[i].items;
+            for(var k = 0; k < gr.length; k++) {
+                gr[k].displayed = 1;
+                for(var j = 0; j < tokens.length; j++) {
+                    if(gr[k].text.toLowerCase.indexOf(tokens[j]) < 0) {
+                        gr[k].displayed = 0;
+                        break;
+                    }
                 }
             }
         }
         SelectBox.redisplay(id);
     },
     delete_from_cache: function(id, value) {
-        var node, delete_index = null;
-        for (var i = 0; (node = SelectBox.cache[id][i]); i++) {
-            if (node.value == value) {
-                delete_index = i;
-                break;
+        var cache = SelectBox.cache[id];
+        outer:
+        for(var i = 0; i < cache.length; i ++) {
+            var gr = cache[i].items;
+            for(var j = 0; j < gr.length; j++) {
+                if(gr[j].value == value) {
+                    gr.splice(j, 1);
+                    break outer;
+                }
             }
         }
-        var j = SelectBox.cache[id].length - 1;
-        for (var i = delete_index; i < j; i++) {
-            SelectBox.cache[id][i] = SelectBox.cache[id][i+1];
-        }
-        SelectBox.cache[id].length--;
     },
-    add_to_cache: function(id, option) {
-        SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1});
-    },
-    cache_contains: function(id, value) {
-        // Check if an item is contained in the cache
-        var node;
-        for (var i = 0; (node = SelectBox.cache[id][i]); i++) {
-            if (node.value == value) {
-                return true;
+    add_to_cache: function(id, group, option) {
+        var cache = SelectBox.cache[id];
+        for(var i = 0; i < cache.length; i++) {
+            if(cache[i].group == group) {
+                cache[i].items.push({value: option.value, text: option.text, displayed: 1});
+                SelectBox.sort(id, group);
+                return;
             }
         }
-        return false;
+        //new group
+        cache.push({'group': group, 'items': [{value: option.value, text: option.text, displayed: 1}]});
     },
     move: function(from, to) {
-        var from_box = document.getElementById(from);
-        var to_box = document.getElementById(to);
-        var option;
-        for (var i = 0; (option = from_box.options[i]); i++) {
-            if (option.selected && SelectBox.cache_contains(from, option.value)) {
-                SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1});
-                SelectBox.delete_from_cache(from, option.value);
-            }
-        }
+        django.jQuery("#" + from + " option:selected").each(function() {
+            var group = this.parentNode.tagName.toLowerCase() == 'optgroup'? this.parentNode.getAttribute('label'): null;
+            SelectBox.add_to_cache(to, group, {value: this.value, text: this.text, displayed: 1});
+            SelectBox.delete_from_cache(from, this.value);
+        });
         SelectBox.redisplay(from);
         SelectBox.redisplay(to);
     },
     move_all: function(from, to) {
-        var from_box = document.getElementById(from);
-        var to_box = document.getElementById(to);
-        var option;
-        for (var i = 0; (option = from_box.options[i]); i++) {
-            if (SelectBox.cache_contains(from, option.value)) {
-                SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1});
-                SelectBox.delete_from_cache(from, option.value);
-            }
-        }
+        django.jQuery("#" + from + " option").each(function() {
+            var group = this.parentNode.tagName.toLowerCase() == 'optgroup'? this.parentNode.getAttribute('label'): null;
+            SelectBox.add_to_cache(to, group, {value: this.value, text: this.text, displayed: 1});
+            SelectBox.delete_from_cache(from, this.value);
+        });
         SelectBox.redisplay(from);
         SelectBox.redisplay(to);
     },
-    sort: function(id) {
-        SelectBox.cache[id].sort( function(a, b) {
-            a = a.text.toLowerCase();
-            b = b.text.toLowerCase();
-            try {
-                if (a > b) return 1;
-                if (a < b) return -1;
+    sort: function(id, group) {
+        var cache = SelectBox.cache[id];
+        for(var i = 0; i < cache.length; i++) {
+            if(group === false || cache[i].group == group) {
+                cache[i].items.sort(function(a, b) {
+                    a = a.text.toLowerCase();
+                    b = b.text.toLowerCase();
+                    try {
+                        if (a > b) return 1;
+                        if (a < b) return -1;
+                    } catch (e) {
+                        // silently fail on IE 'unknown' exception
+                    }
+                    return 0;
+                });
             }
-            catch (e) {
-                // silently fail on IE 'unknown' exception
-            }
-            return 0;
-        } );
+        }
     },
     select_all: function(id) {
-        var box = document.getElementById(id);
-        for (var i = 0; i < box.options.length; i++) {
-            box.options[i].selected = 'selected';
-        }
+        django.jQuery("#" + id + " option").attr('selected', 'selected');
     }
 }
