Index: django/contrib/admin/media/js/selectfilter.js
===================================================================
--- django/contrib/admin/media/js/selectfilter.js	(revision 0)
+++ django/contrib/admin/media/js/selectfilter.js	(revision 0)
@@ -0,0 +1,185 @@
+(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($('<li>')
+					.append($('<a>').addClass('selector-add').text(gettext('Add')))
+				)
+				.append($('<li>')
+					.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.keydown(function(event) {
+				event.which = event.which ? event.which : event.keyCode;
+				switch(event.which) {
+					case 13:
+						// Enter pressed - don't submit the form but move the current selection.
+						settings.select(choices.children().filter(':selected'), choices, selected);
+						this.selectedIndex = (this.selectedIndex == this.length) ? this.length - 1 : this.selectedIndex;
+						return false;
+					case 39:
+						// Right arrow - move across (only when horizontal)
+						if (!settings.is_stacked) {
+							settings.select(choices.children().filter(':selected'), choices, selected);
+							this.selectedIndex = (this.selectedIndex == this.length) ? this.length - 1 : this.selectedIndex;
+							return false;
+						}
+				}
+				return true;
+			});
+
+			selected.keydown(function(event) {
+				event.which = event.which ? event.which : event.keyCode;
+				switch(event.which) {
+					case 13:
+						// Enter pressed - don't submit the form but move the current selection.
+						settings.select(selected.children().filter(':selected'), selected, choices);
+						this.selectedIndex = (this.selectedIndex == this.length) ? this.length - 1 : this.selectedIndex;
+						return false;
+					case 37:
+						// Left arrow - move across (only when horizontal)
+						if (!settings.is_stacked) {
+							settings.select(selected.children().filter(':selected'), selected, choices);
+							this.selectedIndex = (this.selectedIndex == this.length) ? this.length - 1 : this.selectedIndex;
+							return false;
+						}
+				}
+				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/options.py
===================================================================
--- django/contrib/admin/options.py	(revision 16355)
+++ django/contrib/admin/options.py	(working copy)
@@ -1321,7 +1321,7 @@
             js.append('js/urlify.js')
             js.append('js/prepopulate.min.js')
         if self.filter_vertical or self.filter_horizontal:
-            js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
+            js.extend(['js/SelectBox.js' , 'js/selectfilter.js'])
         return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
     media = property(_media)
 
Index: django/contrib/admin/widgets.py
===================================================================
--- django/contrib/admin/widgets.py	(revision 16355)
+++ django/contrib/admin/widgets.py	(working copy)
@@ -22,9 +22,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/selectfilter.js",)
 
     def __init__(self, verbose_name, is_stacked, attrs=None, choices=()):
         self.verbose_name = verbose_name
@@ -35,12 +34,27 @@
         if attrs is None: attrs = {}
         attrs['class'] = 'selectfilter'
         if self.is_stacked: attrs['class'] += 'stacked'
+        id_ = attrs.get('id', 'id_%s' % name)
         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)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>''' % {
+            'id': id_,
+            '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):
@@ -131,11 +145,10 @@
             url = u''
         if "class" not in attrs:
             attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
+        id_ = attrs.get('id', 'id_%s' % name)
         output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)]
-        # TODO: "id_" is hard-coded here. This should instead use the correct
-        # API to determine the ID dynamically.
-        output.append(u'<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \
-            (related_url, url, name))
+        output.append(u'<a href="%s%s" class="related-lookup" id="lookup_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \
+            (related_url, url, id_))
         output.append(u'<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" /></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup')))
         if value:
             output.append(self.label_for_value(value))
@@ -238,10 +251,9 @@
         self.widget.choices = self.choices
         output = [self.widget.render(name, value, *args, **kwargs)]
         if self.can_add_related:
-            # TODO: "id_" is hard-coded here. This should instead use the correct
-            # API to determine the ID dynamically.
-            output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \
-                (related_url, name))
+            id_ = self.attrs.get('id', 'id_%s' % name)
+            output.append(u'<a href="%s" class="add-another" id="add_%s" onclick="return showAddAnotherPopup(this);"> ' % \
+                (related_url, id_))
             output.append(u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="%s"/></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Add Another')))
         return mark_safe(u''.join(output))
 
