final move of files
[web.mtrack] / js / jquery.asmselect.js
1 /*
2  * Alternate Select Multiple (asmSelect) 1.0.4a beta - jQuery Plugin
3  * http://www.ryancramer.com/projects/asmselect/
4  * 
5  * Copyright (c) 2009 by Ryan Cramer - http://www.ryancramer.com
6  * 
7  * Dual licensed under the MIT (MIT-LICENSE.txt)
8  * and GPL (GPL-LICENSE.txt) licenses.
9  *
10  */
11
12 (function($) {
13
14         $.fn.asmSelect = function(customOptions) {
15
16                 var options = {
17
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 
25
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
29
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>
39
40                         };
41
42                 $.extend(options, customOptions); 
43
44                 return this.each(function(index) {
45
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
53
54                         function init() {
55
56                                 // initialize the alternate select multiple
57
58                                 // this loop ensures uniqueness, in case of existing asmSelects placed by ajax (1.0.3)
59                                 while($("#" + options.containerClass + index).size() > 0) index++; 
60
61                                 $select = $("<select></select>")
62                                         .addClass(options.selectClass)
63                                         .attr('name', options.selectClass + index)
64                                         .attr('id', options.selectClass + index); 
65
66                                 $selectRemoved = $("<select></select>"); 
67
68                                 $ol = $("<" + options.listType + "></" + options.listType + ">")
69                                         .addClass(options.listClass)
70                                         .attr('id', options.listClass + index); 
71
72                                 $container = $("<div></div>")
73                                         .addClass(options.containerClass) 
74                                         .attr('id', options.containerClass + index); 
75
76                                 buildSelect();
77
78                                 $select.change(selectChangeEvent)
79                                         .click(selectClickEvent); 
80
81                                 $original.change(originalChangeEvent)
82                                         .wrap($container).before($select).before($ol);
83
84                                 if(options.sortable) makeSortable();
85
86                                 if($.browser.msie && $.browser.version < 8) $ol.css('display', 'inline-block'); // Thanks Matthew Hutton
87                         }
88
89                         function makeSortable() {
90
91                                 // make any items in the selected list sortable
92                                 // requires jQuery UI sortables, draggables, droppables
93
94                                 $ol.sortable({
95                                         items: 'li.' + options.listItemClass,
96                                         handle: '.' + options.listItemLabelClass,
97                                         axis: 'y',
98                                         update: function(e, data) {
99
100                                                 var updatedOptionId;
101
102                                                 $(this).children("li").each(function(n) {
103
104                                                         $option = $('#' + $(this).attr('rel')); 
105
106                                                         if($(this).is(".ui-sortable-helper")) {
107                                                                 updatedOptionId = $option.attr('id'); 
108                                                                 return;
109                                                         }
110
111                                                         $original.append($option); 
112                                                 }); 
113
114                                                 if(updatedOptionId) triggerOriginalChange(updatedOptionId, 'sort'); 
115                                         }
116
117                                 }).addClass(options.listSortableClass); 
118                         }
119
120                         function selectChangeEvent(e) {
121                                 
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
124
125                                 if($.browser.msie && $.browser.version < 7 && !ieClick) return;
126                                 var id = $(this).children("option:selected").slice(0,1).attr('rel'); 
127                                 addListItem(id);        
128                                 ieClick = false; 
129                                 triggerOriginalChange(id, 'add'); // for use by user-defined callbacks
130                         }
131
132                         function selectClickEvent() {
133
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?
137
138                                 ieClick = true; 
139                         }
140
141                         function originalChangeEvent(e) {
142
143                                 // select or option change event manually triggered
144                                 // on the original <select multiple>, so rebuild ours
145
146                                 if(ignoreOriginalChangeEvent) {
147                                         ignoreOriginalChangeEvent = false; 
148                                         return; 
149                                 }
150
151                                 $select.empty();
152                                 $ol.empty();
153                                 buildSelect();
154
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");
158                         }
159
160                         function buildSelect() {
161
162                                 // build or rebuild the new select that the user
163                                 // will select items from
164
165                                 buildingSelect = true; 
166
167                                 // add a first option to be the home option / default selectLabel
168                                 $select.prepend("<option>" + $original.attr('title') + "</option>"); 
169
170                                 $original.children("option").each(function(n) {
171
172                                         var $t = $(this); 
173                                         var id; 
174
175                                         if(!$t.attr('id')) $t.attr('id', 'asm' + index + 'option' + n); 
176                                         id = $t.attr('id'); 
177
178                                         if($t.is(":selected")) {
179                                                 addListItem(id); 
180                                                 addSelectOption(id, true);                                              
181                                         } else {
182                                                 addSelectOption(id); 
183                                         }
184                                 });
185
186                                 if(!options.debugMode) $original.hide(); // IE6 requires this on every buildSelect()
187                                 selectFirstItem();
188                                 buildingSelect = false; 
189                         }
190
191                         function addSelectOption(optionId, disabled) {
192
193                                 // add an <option> to the <select>
194                                 // used only by buildSelect()
195
196                                 if(disabled == undefined) var disabled = false; 
197
198                                 var $O = $('#' + optionId); 
199                                 var $option = $("<option>" + $O.text() + "</option>")
200                                         .val($O.val())
201                                         .attr('rel', optionId);
202
203                                 if(disabled) disableSelectOption($option); 
204
205                                 $select.append($option); 
206                         }
207
208                         function selectFirstItem() {
209
210                                 // select the firm item from the regular select that we created
211
212                                 $select.children(":eq(0)").attr("selected", true); 
213                         }
214
215                         function disableSelectOption($option) {
216
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
220
221                                 $option.addClass(options.optionDisabledClass)
222                                         .attr("selected", false)
223                                         .attr("disabled", true);
224
225                                 if(options.hideWhenAdded) $option.hide();
226                                 if($.browser.msie) $select.hide().show(); // this forces IE to update display
227                         }
228
229                         function enableSelectOption($option) {
230
231                                 // given an already disabled select option, enable it
232
233                                 $option.removeClass(options.optionDisabledClass)
234                                         .attr("disabled", false);
235
236                                 if(options.hideWhenAdded) $option.show();
237                                 if($.browser.msie) $select.hide().show(); // this forces IE to update display
238                         }
239
240                         function addListItem(optionId) {
241
242                                 // add a new item to the html list
243
244                                 var $O = $('#' + optionId); 
245
246                                 if(!$O) return; // this is the first item, selectLabel
247
248                                 var $removeLink = $("<a></a>")
249                                         .attr("href", "#")
250                                         .addClass(options.removeClass)
251                                         .prepend(options.removeLabel)
252                                         .click(function() { 
253                                                 dropListItem($(this).parent('li').attr('rel')); 
254                                                 return false; 
255                                         }); 
256
257                                 var $itemLabel = $("<span></span>")
258                                         .addClass(options.listItemLabelClass)
259                                         .html($O.html()); 
260
261                                 var $item = $("<li></li>")
262                                         .attr('rel', optionId)
263                                         .addClass(options.listItemClass)
264                                         .append($itemLabel)
265                                         .append($removeLink)
266                                         .hide();
267
268                                 if(!buildingSelect) {
269                                         if($O.is(":selected")) return; // already have it
270                                         $O.attr('selected', true); 
271                                 }
272
273                                 if(options.addItemTarget == 'top' && !buildingSelect) {
274                                         $ol.prepend($item); 
275                                         if(options.sortable) $original.prepend($O); 
276                                 } else {
277                                         $ol.append($item); 
278                                         if(options.sortable) $original.append($O); 
279                                 }
280
281                                 addListItemShow($item); 
282
283                                 disableSelectOption($("[rel=" + optionId + "]", $select));
284
285                                 if(!buildingSelect) {
286                                         setHighlight($item, options.highlightAddedLabel); 
287                                         selectFirstItem();
288                                         if(options.sortable) $ol.sortable("refresh");   
289                                 }
290
291                         }
292
293                         function addListItemShow($item) {
294
295                                 // reveal the currently hidden item with optional animation
296                                 // used only by addListItem()
297
298                                 if(options.animate && !buildingSelect) {
299                                         $item.animate({
300                                                 opacity: "show",
301                                                 height: "show"
302                                         }, 100, "swing", function() { 
303                                                 $item.animate({
304                                                         height: "+=2px"
305                                                 }, 50, "swing", function() {
306                                                         $item.animate({
307                                                                 height: "-=2px"
308                                                         }, 25, "swing"); 
309                                                 }); 
310                                         }); 
311                                 } else {
312                                         $item.show();
313                                 }
314                         }
315
316                         function dropListItem(optionId, highlightItem) {
317
318                                 // remove an item from the html list
319
320                                 if(highlightItem == undefined) var highlightItem = true; 
321                                 var $O = $('#' + optionId); 
322
323                                 $O.attr('selected', false); 
324                                 $item = $ol.children("li[rel=" + optionId + "]");
325
326                                 dropListItemHide($item); 
327                                 enableSelectOption($("[rel=" + optionId + "]", options.removeWhenAdded ? $selectRemoved : $select));
328
329                                 if(highlightItem) setHighlight($item, options.highlightRemovedLabel); 
330
331                                 triggerOriginalChange(optionId, 'drop'); 
332                                 
333                         }
334
335                         function dropListItemHide($item) {
336
337                                 // remove the currently visible item with optional animation
338                                 // used only by dropListItem()
339
340                                 if(options.animate && !buildingSelect) {
341
342                                         $prevItem = $item.prev("li");
343
344                                         $item.animate({
345                                                 opacity: "hide",
346                                                 height: "hide"
347                                         }, 100, "linear", function() {
348                                                 $prevItem.animate({
349                                                         height: "-=2px"
350                                                 }, 50, "swing", function() {
351                                                         $prevItem.animate({
352                                                                 height: "+=2px"
353                                                         }, 100, "swing"); 
354                                                 }); 
355                                                 $item.remove(); 
356                                         }); 
357                                         
358                                 } else {
359                                         $item.remove(); 
360                                 }
361                         }
362
363                         function setHighlight($item, label) {
364
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
368
369                                 if(!options.highlight) return; 
370
371                                 $select.next("#" + options.highlightClass + index).remove();
372
373                                 var $highlight = $("<span></span>")
374                                         .hide()
375                                         .addClass(options.highlightClass)
376                                         .attr('id', options.highlightClass + index)
377                                         .html(label + $item.children("." + options.listItemLabelClass).slice(0,1).text()); 
378                                         
379                                 $select.after($highlight); 
380
381                                 $highlight.fadeIn("fast", function() {
382                                         setTimeout(function() { $highlight.fadeOut("slow"); }, 50); 
383                                 }); 
384                         }
385
386                         function triggerOriginalChange(optionId, type) {
387
388                                 // trigger a change event on the original select multiple
389                                 // so that other scripts can pick them up
390
391                                 ignoreOriginalChangeEvent = true; 
392                                 $option = $("#" + optionId); 
393
394                                 $original.trigger('change', [{
395                                         'option': $option,
396                                         'value': $option.val(),
397                                         'id': optionId,
398                                         'item': $ol.children("[rel=" + optionId + "]"),
399                                         'type': type
400                                 }]); 
401                         }
402
403                         init();
404                 });
405         };
406
407 })(jQuery);