2 * Alternate Select Multiple (asmSelect) 1.0.4a beta - jQuery Plugin
3 * http://www.ryancramer.com/projects/asmselect/
5 * Copyright (c) 2009 by Ryan Cramer - http://www.ryancramer.com
7 * Dual licensed under the MIT (MIT-LICENSE.txt)
8 * and GPL (GPL-LICENSE.txt) licenses.
14 $.fn.asmSelect = function(customOptions) {
18 listType: 'ol', // Ordered list 'ol', or unordered list 'ul'
19 sortable: false, // Should the list be sortable?
20 highlight: false, // Use the highlight feature?
21 animate: false, // Animate the the adding/removing of items in the list?
22 addItemTarget: 'bottom', // Where to place new selected items in list: top or bottom
23 hideWhenAdded: false, // Hide the option when added to the list? works only in FF
24 debugMode: false, // Debug mode keeps original select visible
26 removeLabel: 'remove', // Text used in the "remove" link
27 highlightAddedLabel: 'Added: ', // Text that precedes highlight of added item
28 highlightRemovedLabel: 'Removed: ', // Text that precedes highlight of removed item
30 containerClass: 'asmContainer', // Class for container that wraps this widget
31 selectClass: 'asmSelect', // Class for the newly created <select>
32 optionDisabledClass: 'asmOptionDisabled', // Class for items that are already selected / disabled
33 listClass: 'asmList', // Class for the list ($ol)
34 listSortableClass: 'asmListSortable', // Another class given to the list when it is sortable
35 listItemClass: 'asmListItem', // Class for the <li> list items
36 listItemLabelClass: 'asmListItemLabel', // Class for the label text that appears in list items
37 removeClass: 'asmListItemRemove', // Class given to the "remove" link
38 highlightClass: 'asmHighlight' // Class given to the highlight <span>
42 $.extend(options, customOptions);
44 return this.each(function(index) {
46 var $original = $(this); // the original select multiple
47 var $container; // a container that is wrapped around our widget
48 var $select; // the new select we have created
49 var $ol; // the list that we are manipulating
50 var buildingSelect = false; // is the new select being constructed right now?
51 var ieClick = false; // in IE, has a click event occurred? ignore if not
52 var ignoreOriginalChangeEvent = false; // originalChangeEvent bypassed when this is true
56 // initialize the alternate select multiple
58 // this loop ensures uniqueness, in case of existing asmSelects placed by ajax (1.0.3)
59 while($("#" + options.containerClass + index).size() > 0) index++;
61 $select = $("<select></select>")
62 .addClass(options.selectClass)
63 .attr('name', options.selectClass + index)
64 .attr('id', options.selectClass + index);
66 $selectRemoved = $("<select></select>");
68 $ol = $("<" + options.listType + "></" + options.listType + ">")
69 .addClass(options.listClass)
70 .attr('id', options.listClass + index);
72 $container = $("<div></div>")
73 .addClass(options.containerClass)
74 .attr('id', options.containerClass + index);
78 $select.change(selectChangeEvent)
79 .click(selectClickEvent);
81 $original.change(originalChangeEvent)
82 .wrap($container).before($select).before($ol);
84 if(options.sortable) makeSortable();
86 if($.browser.msie && $.browser.version < 8) $ol.css('display', 'inline-block'); // Thanks Matthew Hutton
89 function makeSortable() {
91 // make any items in the selected list sortable
92 // requires jQuery UI sortables, draggables, droppables
95 items: 'li.' + options.listItemClass,
96 handle: '.' + options.listItemLabelClass,
98 update: function(e, data) {
102 $(this).children("li").each(function(n) {
104 $option = $('#' + $(this).attr('rel'));
106 if($(this).is(".ui-sortable-helper")) {
107 updatedOptionId = $option.attr('id');
111 $original.append($option);
114 if(updatedOptionId) triggerOriginalChange(updatedOptionId, 'sort');
117 }).addClass(options.listSortableClass);
120 function selectChangeEvent(e) {
122 // an item has been selected on the regular select we created
123 // check to make sure it's not an IE screwup, and add it to the list
125 if($.browser.msie && $.browser.version < 7 && !ieClick) return;
126 var id = $(this).children("option:selected").slice(0,1).attr('rel');
129 triggerOriginalChange(id, 'add'); // for use by user-defined callbacks
132 function selectClickEvent() {
134 // IE6 lets you scroll around in a select without it being pulled down
135 // making sure a click preceded the change() event reduces the chance
136 // if unintended items being added. there may be a better solution?
141 function originalChangeEvent(e) {
143 // select or option change event manually triggered
144 // on the original <select multiple>, so rebuild ours
146 if(ignoreOriginalChangeEvent) {
147 ignoreOriginalChangeEvent = false;
155 // opera has an issue where it needs a force redraw, otherwise
156 // the items won't appear until something else forces a redraw
157 if($.browser.opera) $ol.hide().fadeIn("fast");
160 function buildSelect() {
162 // build or rebuild the new select that the user
163 // will select items from
165 buildingSelect = true;
167 // add a first option to be the home option / default selectLabel
168 $select.prepend("<option>" + $original.attr('title') + "</option>");
170 $original.children("option").each(function(n) {
175 if(!$t.attr('id')) $t.attr('id', 'asm' + index + 'option' + n);
178 if($t.is(":selected")) {
180 addSelectOption(id, true);
186 if(!options.debugMode) $original.hide(); // IE6 requires this on every buildSelect()
188 buildingSelect = false;
191 function addSelectOption(optionId, disabled) {
193 // add an <option> to the <select>
194 // used only by buildSelect()
196 if(disabled == undefined) var disabled = false;
198 var $O = $('#' + optionId);
199 var $option = $("<option>" + $O.text() + "</option>")
201 .attr('rel', optionId);
203 if(disabled) disableSelectOption($option);
205 $select.append($option);
208 function selectFirstItem() {
210 // select the firm item from the regular select that we created
212 $select.children(":eq(0)").attr("selected", true);
215 function disableSelectOption($option) {
217 // make an option disabled, indicating that it's already been selected
218 // because safari is the only browser that makes disabled items look 'disabled'
219 // we apply a class that reproduces the disabled look in other browsers
221 $option.addClass(options.optionDisabledClass)
222 .attr("selected", false)
223 .attr("disabled", true);
225 if(options.hideWhenAdded) $option.hide();
226 if($.browser.msie) $select.hide().show(); // this forces IE to update display
229 function enableSelectOption($option) {
231 // given an already disabled select option, enable it
233 $option.removeClass(options.optionDisabledClass)
234 .attr("disabled", false);
236 if(options.hideWhenAdded) $option.show();
237 if($.browser.msie) $select.hide().show(); // this forces IE to update display
240 function addListItem(optionId) {
242 // add a new item to the html list
244 var $O = $('#' + optionId);
246 if(!$O) return; // this is the first item, selectLabel
248 var $removeLink = $("<a></a>")
250 .addClass(options.removeClass)
251 .prepend(options.removeLabel)
253 dropListItem($(this).parent('li').attr('rel'));
257 var $itemLabel = $("<span></span>")
258 .addClass(options.listItemLabelClass)
261 var $item = $("<li></li>")
262 .attr('rel', optionId)
263 .addClass(options.listItemClass)
268 if(!buildingSelect) {
269 if($O.is(":selected")) return; // already have it
270 $O.attr('selected', true);
273 if(options.addItemTarget == 'top' && !buildingSelect) {
275 if(options.sortable) $original.prepend($O);
278 if(options.sortable) $original.append($O);
281 addListItemShow($item);
283 disableSelectOption($("[rel=" + optionId + "]", $select));
285 if(!buildingSelect) {
286 setHighlight($item, options.highlightAddedLabel);
288 if(options.sortable) $ol.sortable("refresh");
293 function addListItemShow($item) {
295 // reveal the currently hidden item with optional animation
296 // used only by addListItem()
298 if(options.animate && !buildingSelect) {
302 }, 100, "swing", function() {
305 }, 50, "swing", function() {
316 function dropListItem(optionId, highlightItem) {
318 // remove an item from the html list
320 if(highlightItem == undefined) var highlightItem = true;
321 var $O = $('#' + optionId);
323 $O.attr('selected', false);
324 $item = $ol.children("li[rel=" + optionId + "]");
326 dropListItemHide($item);
327 enableSelectOption($("[rel=" + optionId + "]", options.removeWhenAdded ? $selectRemoved : $select));
329 if(highlightItem) setHighlight($item, options.highlightRemovedLabel);
331 triggerOriginalChange(optionId, 'drop');
335 function dropListItemHide($item) {
337 // remove the currently visible item with optional animation
338 // used only by dropListItem()
340 if(options.animate && !buildingSelect) {
342 $prevItem = $item.prev("li");
347 }, 100, "linear", function() {
350 }, 50, "swing", function() {
363 function setHighlight($item, label) {
365 // set the contents of the highlight area that appears
366 // directly after the <select> single
367 // fade it in quickly, then fade it out
369 if(!options.highlight) return;
371 $select.next("#" + options.highlightClass + index).remove();
373 var $highlight = $("<span></span>")
375 .addClass(options.highlightClass)
376 .attr('id', options.highlightClass + index)
377 .html(label + $item.children("." + options.listItemLabelClass).slice(0,1).text());
379 $select.after($highlight);
381 $highlight.fadeIn("fast", function() {
382 setTimeout(function() { $highlight.fadeOut("slow"); }, 50);
386 function triggerOriginalChange(optionId, type) {
388 // trigger a change event on the original select multiple
389 // so that other scripts can pick them up
391 ignoreOriginalChangeEvent = true;
392 $option = $("#" + optionId);
394 $original.trigger('change', [{
396 'value': $option.val(),
398 'item': $ol.children("[rel=" + optionId + "]"),