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
.crmasmSelect = function(customOptions
) {
18 listType
: 'ul', // 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
: 'X', // 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
: 'crmasmContainer', // Class for container that wraps this widget
31 selectClass
: 'crmasmSelect', // Class for the newly created <select>
32 optionDisabledClass
: 'asmOptionDisabled', // Class for items that are already selected / disabled
33 listClass
: 'crmasmList', // Class for the list ($ol)
34 listSortableClass
: 'crmasmListSortable', // Another class given to the list when it is sortable
35 listItemClass
: 'crmasmListItem', // Class for the <li> list items
36 listItemLabelClass
: 'crmasmListItemLabel', // Class for the label text that appears in list items
37 removeClass
: 'crmasmListItemRemove', // Class given to the "remove" link
38 highlightClass
: 'crm asmHighlight', // Class given to the highlight <span>
40 respectParents
: false // Force selection of "parent" option items
43 $.extend(options
, customOptions
);
45 return this.each(function(index
) {
47 var $original
= $(this); // the original select multiple
48 var $container
; // a container that is wrapped around our widget
49 var $select
; // the new select we have created
50 var $ol
; // the list that we are manipulating
51 var buildingSelect
= false; // is the new select being constructed right now?
52 var ieClick
= false; // in IE, has a click event occurred? ignore if not
53 var ignoreOriginalChangeEvent
= false; // originalChangeEvent bypassed when this is true
57 // initialize the alternate select multiple
59 // this loop ensures uniqueness, in case of existing crmasmSelects placed by ajax (1.0.3)
60 while($("#" + options
.containerClass
+ index
).size() > 0) index
++;
62 $select
= $("<select></select>")
63 .addClass(options
.selectClass
)
64 .attr('name', options
.selectClass
+ index
)
65 .attr('id', options
.selectClass
+ index
);
67 $selectRemoved
= $("<select></select>");
69 $ol
= $("<" + options
.listType
+ "></" + options
.listType
+ ">")
70 .addClass(options
.listClass
)
71 .attr('id', options
.listClass
+ index
);
73 $container
= $("<div></div>")
74 .addClass(options
.containerClass
)
75 .attr('id', options
.containerClass
+ index
);
79 $select
.change(selectChangeEvent
)
80 .click(selectClickEvent
);
82 $original
.change(originalChangeEvent
)
83 .wrap($container
).before($select
).before($ol
);
85 if(options
.sortable
) makeSortable();
87 if($.browser
.msie
&& $.browser
.version
< 8) $ol
.css('display', 'inline-block'); // Thanks Matthew Hutton
90 function makeSortable() {
92 // make any items in the selected list sortable
93 // requires jQuery UI sortables, draggables, droppables
96 items
: 'li.' + options
.listItemClass
,
97 handle
: '.' + options
.listItemLabelClass
,
99 update: function(e
, data
) {
103 $(this).children("li").each(function(n
) {
105 $option
= $('#' + $(this).attr('rel'));
107 if($(this).is(".ui-sortable-helper")) {
108 updatedOptionId
= $option
.attr('id');
112 $original
.append($option
);
115 if(updatedOptionId
) triggerOriginalChange(updatedOptionId
, 'sort');
118 }).addClass(options
.listSortableClass
);
121 function selectChangeEvent(e
) {
123 // an item has been selected on the regular select we created
124 // check to make sure it's not an IE screwup, and add it to the list
126 if($.browser
.msie
&& $.browser
.version
< 7 && !ieClick
) return;
127 var id
= $(this).children("option:selected").slice(0,1).attr('rel');
130 triggerOriginalChange(id
, 'add'); // for use by user-defined callbacks
133 function selectClickEvent() {
135 // IE6 lets you scroll around in a select without it being pulled down
136 // making sure a click preceded the change() event reduces the chance
137 // if unintended items being added. there may be a better solution?
142 function originalChangeEvent(e
) {
144 // select or option change event manually triggered
145 // on the original <select multiple>, so rebuild ours
147 if(ignoreOriginalChangeEvent
) {
148 ignoreOriginalChangeEvent
= false;
156 // opera has an issue where it needs a force redraw, otherwise
157 // the items won't appear until something else forces a redraw
158 if($.browser
.opera
) $ol
.hide().fadeIn("fast");
161 function buildSelect() {
163 // build or rebuild the new select that the user
164 // will select items from
166 buildingSelect
= true;
168 // add a first option to be the home option / default selectLabel
169 $select
.prepend("<option>" + $original
.attr('title') + "</option>");
171 $original
.children("option").each(function(n
) {
176 if(!$t
.attr('id')) $t
.attr('id', 'asm' + index
+ 'option' + n
);
178 classes
= $t
.attr('class');
180 if($t
.is(":selected")) {
182 addSelectOption(id
, classes
, true);
184 addSelectOption(id
,classes
);
188 if(!options
.debugMode
) $original
.hide(); // IE6 requires this on every buildSelect()
190 buildingSelect
= false;
193 function addSelectOption(optionId
, optionClasses
, disabled
) {
195 // add an <option> to the <select>
196 // used only by buildSelect()
198 if(disabled
== undefined) disabled
= false;
200 var $O
= $('#' + optionId
);
201 var $option
= $("<option>" + $O
.text() + "</option>")
203 .attr('rel', optionId
)
204 .addClass(optionClasses
);
206 if(disabled
) disableSelectOption($option
);
208 $select
.append($option
);
211 function selectFirstItem() {
213 // select the firm item from the regular select that we created
215 $select
.children(":eq(0)").prop("selected", true);
218 function disableSelectOption($option
) {
220 // make an option disabled, indicating that it's already been selected
221 // because safari is the only browser that makes disabled items look 'disabled'
222 // we apply a class that reproduces the disabled look in other browsers
224 $option
.addClass(options
.optionDisabledClass
)
225 .prop("selected", false)
226 .prop("disabled", true);
228 if(options
.hideWhenAdded
) $option
.hide();
229 if($.browser
.msie
) $select
.hide().show(); // this forces IE to update display
232 function enableSelectOption($option
) {
234 // given an already disabled select option, enable it
236 $option
.removeClass(options
.optionDisabledClass
)
237 .prop("disabled", false);
239 if(options
.hideWhenAdded
) $option
.show();
240 if($.browser
.msie
) $select
.hide().show(); // this forces IE to update display
243 function addListItem(optionId
) {
245 // add a new item to the html list
247 var $O
= $('#' + optionId
);
249 if(!$O
) return; // this is the first item, selectLabel
251 var $removeLink
= $("<a></a>")
253 .addClass(options
.removeClass
)
254 .prepend(options
.removeLabel
)
256 dropListItem($(this).parent('li').attr('rel'));
260 var $itemLabel
= $("<span></span>")
261 .addClass(options
.listItemLabelClass
)
264 var $item
= $("<li></li>")
265 .attr('rel', optionId
)
266 .addClass(options
.listItemClass
)
267 .addClass($O
.attr('class'))
272 if(!buildingSelect
) {
273 if($O
.is(":selected")) return; // already have it
274 $O
.prop('selected', true);
277 if(options
.addItemTarget
== 'top' && !buildingSelect
) {
279 if(options
.sortable
) $original
.prepend($O
);
282 if(options
.sortable
) $original
.append($O
);
285 addListItemShow($item
);
287 disableSelectOption($("[rel=" + optionId
+ "]", $select
));
289 if(!buildingSelect
) {
290 setHighlight($item
, options
.highlightAddedLabel
);
292 if(options
.sortable
) $ol
.sortable("refresh");
295 if(options
.respectParents
) {
297 if($O
.hasClass('child')) {
298 parentName
= $O
.attr('class').split('parent-')[1];
299 parentName
= parentName
.split(' ')[0];
300 parentId
= $('.option-'+parentName
).attr('rel');
301 addListItem(parentId
);
306 function addListItemShow($item
) {
308 // reveal the currently hidden item with optional animation
309 // used only by addListItem()
311 if(options
.animate
&& !buildingSelect
) {
315 }, 100, "swing", function() {
318 }, 50, "swing", function() {
329 function dropListItem(optionId
, highlightItem
) {
331 // remove an item from the html list
333 if(highlightItem
== undefined) highlightItem
= true;
334 var $O
= $('#' + optionId
);
336 $O
.prop('selected', false);
337 $item
= $ol
.children("li[rel=" + optionId
+ "]");
339 dropListItemHide($item
);
340 enableSelectOption($("[rel=" + optionId
+ "]", options
.removeWhenAdded
? $selectRemoved
: $select
));
342 if(highlightItem
) setHighlight($item
, options
.highlightRemovedLabel
);
344 triggerOriginalChange(optionId
, 'drop');
348 function dropListItemHide($item
) {
350 // remove the currently visible item with optional animation
351 // used only by dropListItem()
353 if(options
.animate
&& !buildingSelect
) {
355 $prevItem
= $item
.prev("li");
360 }, 100, "linear", function() {
363 }, 50, "swing", function() {
376 function setHighlight($item
, label
) {
378 // set the contents of the highlight area that appears
379 // directly after the <select> single
380 // fade it in quickly, then fade it out
382 if(!options
.highlight
) return;
384 $select
.next("#" + options
.highlightClass
+ index
).remove();
386 var $highlight
= $("<span></span>")
388 .addClass(options
.highlightClass
)
389 .attr('id', options
.highlightClass
+ index
)
390 .html(label
+ $item
.children("." + options
.listItemLabelClass
).slice(0,1).text());
392 $select
.after($highlight
);
394 $highlight
.fadeIn("fast", function() {
395 setTimeout(function() { $highlight
.fadeOut("slow"); }, 50);
399 function triggerOriginalChange(optionId
, type
) {
401 // trigger a change event on the original select multiple
402 // so that other scripts can pick them up
404 ignoreOriginalChangeEvent
= true;
405 $option
= $("#" + optionId
);
407 $original
.trigger('change', [{
409 'value': $option
.val(),
411 'item': $ol
.children("[rel=" + optionId
+ "]"),