Index: django/contrib/admin/media/js/jquery.selectfilter.js
===================================================================
--- django/contrib/admin/media/js/jquery.selectfilter.js	(revision 0)
+++ django/contrib/admin/media/js/jquery.selectfilter.js	(revision 0)
@@ -0,0 +1,172 @@
+(function($) {
+  var settings = {
+    'name': '',
+    'verbose_name': '',
+    'is_stacked': 0,
+    'admin_media_prefix': '',
+    'select': function select(options, from, to) {
+      SelectBox.move(from.attr('id'), to.attr('id'));
+      SelectBox.sort(to.attr('id'));
+      SelectBox.redisplay(to.attr('id'));
+    },
+    'deselect': function deselect(options, from, to) {
+      SelectBox.move(from.attr('id'), to.attr('id'));
+      SelectBox.sort(to.attr('id'));
+      SelectBox.redisplay(to.attr('id'));
+    },
+  }
+  
+  var methods = {
+    init: function(options) {
+      $.extend(settings, options);
+      var choices = this;
+      var stack_class = settings.is_stacked ? 'selector stacked' : 'selector';
+      var selector = $('<div>').addClass(stack_class);
+      
+      choices.parent().append(selector);
+      choices.attr({
+        'id': choices.attr('id') + '_from',
+        'name': choices.attr('name') + '_old'
+      });
+
+      var search = $('<input>').attr({
+        'id': "id_" + settings.name + '_input',
+        'type': "text",
+      });
+      
+      $('<div>').addClass('selector-available')
+        .append($('<h2>').text(gettext('Available ') + settings.verbose_name))
+        .append(
+          $('<p>').addClass('selector-filter')
+            .append(
+              $('<label>').attr('for', search.attr('id')).css({'width': "16px", 'padding': "2px"})
+                .append(
+                  $('<img>').attr('src', settings.admin_media_prefix + 'img/admin/selector-search.gif')
+                )
+            )
+            .append(search)
+        ).append(choices)
+        .append($('<a>').addClass('selector-chooseall').text(gettext('Choose all')).attr('href', '#'))
+        .appendTo(selector);
+      
+      $('<ul>').addClass('selector-chooser')
+        .append($('<a>').addClass('selector-add').text(gettext('Add')))
+        .append($('<a>').addClass('selector-remove').text(gettext('Remove')))
+        .appendTo(selector);
+
+      var selected = $('<select>').addClass('filtered').attr({
+        'id': 'id_' + settings.name + '_to',
+        'multiple': 'multiple',
+        'name': settings.name
+      });
+      
+      $('<div>').addClass('selector-chosen')
+        .append($('<h2>').text('Chosen ' + settings.verbose_name))
+        .append(
+          $('<p>').addClass('selector-filter')
+            .text(gettext('Select your choice(s) and click '))
+            .append(
+              $('<img>').attr({
+                'src': settings.admin_media_prefix + 'img/admin/selector-add.gif',
+                'alt': gettext('Add')
+              })
+            )
+        ).append(selected)
+        .append(
+          $('<a>').addClass('selector-clearall')
+            .text(gettext('Clear all'))
+            .attr('href', '#')
+        ).appendTo(selector);
+
+
+      SelectBox.init(choices.attr('id'));
+      SelectBox.init(selected.attr('id'));
+      // if this is a saved instance, move the already selected options across
+      // to the selected list.
+      settings.select(choices.children().filter(':selected'), choices, selected);
+      
+      // hook up selection events.
+      $(search).keyup(function(event) {
+        if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) {
+          settings.select(choices.children().filter(':selected'), choices, selected);
+          return false;
+        }
+        SelectBox.filter(choices.attr('id'), $(this).val());
+        return true;
+      });
+      $('.selector-chooseall').click(function() {
+        settings.select(choices.children(), choices, selected);
+        return false;
+      })
+      
+      $('.selector-clearall').click(function() {
+        settings.deselect(selected.children(), selected, choices);
+        return false;
+      })
+      
+      $('.selector-add').click(function() {
+        settings.select(choices.children().filter(':selected'), choices, selected);
+        return false;
+      });
+      
+      $('.selector-remove').click(function() {
+        settings.deselect(selected.children().filter(':selected'), selected, choices);
+        return false;
+      });
+      
+      choices.dblclick(function() {
+        settings.select(choices.children().filter(':selected'), choices, selected);
+      });
+      
+      selected.dblclick(function() {
+        settings.deselect(selected.children().filter(':selected'), selected, choices);
+      });
+      
+      choices.keypress(function(event) {
+        event.which = event.which ? event.which : event.keyCode;
+        switch(event.which) {
+          case 13:
+            // enter pressed -- don't submit form but move current selection.
+            var temp = this.selectedIndex;
+            settings.select(choices.children().filter(':selected'), choices, selected);
+            this.selectedIndex = (temp == this.length) ? this.length - 1 : temp;
+            return false;
+            break;
+          case 38:
+            // up arrow -- wrap around
+            this.selectedIndex = (this.selectedIndex == 0) ? this.length - 1 : this.selectedIndex;
+            break;
+          case 39:
+            // right arrow -- move across
+            var temp = this.selectedIndex;
+            settings.select(choices.children().filter(':selected'), choices, selected);
+            this.selectedIndex = (temp == this.length) ? this.length - 1 : temp;
+            return false;
+            break;
+          case 40:
+            // down arrow -- wrap around
+            this.selectedIndex = (this.length == this.selectedIndex + 1) ? 0 : this.selectedIndex;
+            break;
+        }
+        return true;
+      });
+      
+      $('form').submit(function() {
+        selected.children().each(function(i, option) {
+          $(option).attr('selected', 'selected');
+        });
+        return true;
+      });
+    },
+  }
+  
+  $.fn.selectFilter = function(method) {
+    if (methods[method]) {
+      return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+    } else if (typeof method === 'object' || !method ) {
+      return methods.init.apply(this, arguments);
+    } else {
+      $.error('Method ' + method + ' does not exist on jQuery.selectFilter');
+    }
+  };
+})(django.jQuery);
Index: django/contrib/admin/widgets.py
===================================================================
--- django/contrib/admin/widgets.py	(revision 15403)
+++ django/contrib/admin/widgets.py	(working copy)
@@ -23,9 +23,8 @@
     catalog has been loaded in the page
     """
     class Media:
-        js = (settings.ADMIN_MEDIA_PREFIX + "js/core.js",
-              settings.ADMIN_MEDIA_PREFIX + "js/SelectBox.js",
-              settings.ADMIN_MEDIA_PREFIX + "js/SelectFilter2.js")
+        js = (settings.ADMIN_MEDIA_PREFIX + 'js/SelectBox.js',
+              settings.ADMIN_MEDIA_PREFIX + "js/jquery.selectfilter.js",)
 
     def __init__(self, verbose_name, is_stacked, attrs=None, choices=()):
         self.verbose_name = verbose_name
@@ -37,11 +36,26 @@
         attrs['class'] = 'selectfilter'
         if self.is_stacked: attrs['class'] += 'stacked'
         output = [super(FilteredSelectMultiple, self).render(name, value, attrs, choices)]
-        output.append(u'<script type="text/javascript">addEvent(window, "load", function(e) {')
         # TODO: "id_" is hard-coded here. This should instead use the correct
         # API to determine the ID dynamically.
-        output.append(u'SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % \
-            (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX))
+        output.append(u'''
+<script type="text/javascript">
+(function($) {
+  $(function() {
+    $("#id_%(name)s").selectFilter({
+      'name': "%(name)s",
+      'verbose_name': "%(verbose_name)s",
+      'is_stacked': %(is_stacked)s,
+      'admin_media_prefix': "%(admin_media_prefix)s",
+    });
+  });
+})(django.jQuery);
+</script>''' % {
+            'name': name,
+            'verbose_name': self.verbose_name.replace('"', '\\"'),
+            'is_stacked': int(self.is_stacked),
+            'admin_media_prefix': settings.ADMIN_MEDIA_PREFIX
+        })
         return mark_safe(u''.join(output))
 
 class AdminDateWidget(forms.DateInput):
