--- /dev/null
+/*
+ * Alternate Select Multiple (asmSelect) 1.0.4a beta - jQuery Plugin
+ * http://www.ryancramer.com/projects/asmselect/
+ *
+ * Copyright (c) 2009 by Ryan Cramer - http://www.ryancramer.com
+ *
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ */
+
+(function($) {
+
+ $.fn.asmSelect = function(customOptions) {
+
+ var options = {
+
+ listType: 'ol', // Ordered list 'ol', or unordered list 'ul'
+ sortable: false, // Should the list be sortable?
+ highlight: false, // Use the highlight feature?
+ animate: false, // Animate the the adding/removing of items in the list?
+ addItemTarget: 'bottom', // Where to place new selected items in list: top or bottom
+ hideWhenAdded: false, // Hide the option when added to the list? works only in FF
+ debugMode: false, // Debug mode keeps original select visible
+
+ removeLabel: 'remove', // Text used in the "remove" link
+ highlightAddedLabel: 'Added: ', // Text that precedes highlight of added item
+ highlightRemovedLabel: 'Removed: ', // Text that precedes highlight of removed item
+
+ containerClass: 'asmContainer', // Class for container that wraps this widget
+ selectClass: 'asmSelect', // Class for the newly created <select>
+ optionDisabledClass: 'asmOptionDisabled', // Class for items that are already selected / disabled
+ listClass: 'asmList', // Class for the list ($ol)
+ listSortableClass: 'asmListSortable', // Another class given to the list when it is sortable
+ listItemClass: 'asmListItem', // Class for the <li> list items
+ listItemLabelClass: 'asmListItemLabel', // Class for the label text that appears in list items
+ removeClass: 'asmListItemRemove', // Class given to the "remove" link
+ highlightClass: 'asmHighlight' // Class given to the highlight <span>
+
+ };
+
+ $.extend(options, customOptions);
+
+ return this.each(function(index) {
+
+ var $original = $(this); // the original select multiple
+ var $container; // a container that is wrapped around our widget
+ var $select; // the new select we have created
+ var $ol; // the list that we are manipulating
+ var buildingSelect = false; // is the new select being constructed right now?
+ var ieClick = false; // in IE, has a click event occurred? ignore if not
+ var ignoreOriginalChangeEvent = false; // originalChangeEvent bypassed when this is true
+
+ function init() {
+
+ // initialize the alternate select multiple
+
+ // this loop ensures uniqueness, in case of existing asmSelects placed by ajax (1.0.3)
+ while($("#" + options.containerClass + index).size() > 0) index++;
+
+ $select = $("<select></select>")
+ .addClass(options.selectClass)
+ .attr('name', options.selectClass + index)
+ .attr('id', options.selectClass + index);
+
+ $selectRemoved = $("<select></select>");
+
+ $ol = $("<" + options.listType + "></" + options.listType + ">")
+ .addClass(options.listClass)
+ .attr('id', options.listClass + index);
+
+ $container = $("<div></div>")
+ .addClass(options.containerClass)
+ .attr('id', options.containerClass + index);
+
+ buildSelect();
+
+ $select.change(selectChangeEvent)
+ .click(selectClickEvent);
+
+ $original.change(originalChangeEvent)
+ .wrap($container).before($select).before($ol);
+
+ if(options.sortable) makeSortable();
+
+ if($.browser.msie && $.browser.version < 8) $ol.css('display', 'inline-block'); // Thanks Matthew Hutton
+ }
+
+ function makeSortable() {
+
+ // make any items in the selected list sortable
+ // requires jQuery UI sortables, draggables, droppables
+
+ $ol.sortable({
+ items: 'li.' + options.listItemClass,
+ handle: '.' + options.listItemLabelClass,
+ axis: 'y',
+ update: function(e, data) {
+
+ var updatedOptionId;
+
+ $(this).children("li").each(function(n) {
+
+ $option = $('#' + $(this).attr('rel'));
+
+ if($(this).is(".ui-sortable-helper")) {
+ updatedOptionId = $option.attr('id');
+ return;
+ }
+
+ $original.append($option);
+ });
+
+ if(updatedOptionId) triggerOriginalChange(updatedOptionId, 'sort');
+ }
+
+ }).addClass(options.listSortableClass);
+ }
+
+ function selectChangeEvent(e) {
+
+ // an item has been selected on the regular select we created
+ // check to make sure it's not an IE screwup, and add it to the list
+
+ if($.browser.msie && $.browser.version < 7 && !ieClick) return;
+ var id = $(this).children("option:selected").slice(0,1).attr('rel');
+ addListItem(id);
+ ieClick = false;
+ triggerOriginalChange(id, 'add'); // for use by user-defined callbacks
+ }
+
+ function selectClickEvent() {
+
+ // IE6 lets you scroll around in a select without it being pulled down
+ // making sure a click preceded the change() event reduces the chance
+ // if unintended items being added. there may be a better solution?
+
+ ieClick = true;
+ }
+
+ function originalChangeEvent(e) {
+
+ // select or option change event manually triggered
+ // on the original <select multiple>, so rebuild ours
+
+ if(ignoreOriginalChangeEvent) {
+ ignoreOriginalChangeEvent = false;
+ return;
+ }
+
+ $select.empty();
+ $ol.empty();
+ buildSelect();
+
+ // opera has an issue where it needs a force redraw, otherwise
+ // the items won't appear until something else forces a redraw
+ if($.browser.opera) $ol.hide().fadeIn("fast");
+ }
+
+ function buildSelect() {
+
+ // build or rebuild the new select that the user
+ // will select items from
+
+ buildingSelect = true;
+
+ // add a first option to be the home option / default selectLabel
+ $select.prepend("<option>" + $original.attr('title') + "</option>");
+
+ $original.children("option").each(function(n) {
+
+ var $t = $(this);
+ var id;
+
+ if(!$t.attr('id')) $t.attr('id', 'asm' + index + 'option' + n);
+ id = $t.attr('id');
+ origClass = $t.attr('class');
+
+ if($t.is(":selected")) {
+ addListItem(id);
+ addSelectOption(id, origClass, true);
+ } else {
+ addSelectOption(id, origClass);
+ }
+ });
+
+ if(!options.debugMode) $original.hide(); // IE6 requires this on every buildSelect()
+ selectFirstItem();
+ buildingSelect = false;
+ }
+
+ function addSelectOption(optionId, origClass, disabled) {
+
+ // add an <option> to the <select>
+ // used only by buildSelect()
+
+ if(disabled == undefined) var disabled = false;
+
+ if(origClass == undefined) origClass = '';
+
+ var $O = $('#' + optionId);
+ var $option = $("<option class=\""+origClass+"\">" + $O.text() + "</option>")
+ .val($O.val())
+ .attr('rel', optionId);
+
+ if(disabled) disableSelectOption($option);
+
+ $select.append($option);
+ }
+
+ function selectFirstItem() {
+
+ // select the firm item from the regular select that we created
+
+ $select.children(":eq(0)").attr("selected", true);
+ }
+
+ function disableSelectOption($option) {
+
+ // make an option disabled, indicating that it's already been selected
+ // because safari is the only browser that makes disabled items look 'disabled'
+ // we apply a class that reproduces the disabled look in other browsers
+
+ $option.addClass(options.optionDisabledClass)
+ .attr("selected", false)
+ .attr("disabled", true);
+
+ if(options.hideWhenAdded) $option.hide();
+ if($.browser.msie) $select.hide().show(); // this forces IE to update display
+ }
+
+ function enableSelectOption($option) {
+
+ // given an already disabled select option, enable it
+
+ $option.removeClass(options.optionDisabledClass)
+ .attr("disabled", false);
+
+ if(options.hideWhenAdded) $option.show();
+ if($.browser.msie) $select.hide().show(); // this forces IE to update display
+ }
+
+ function addListItem(optionId) {
+
+ // add a new item to the html list
+
+ var $O = $('#' + optionId);
+
+ if(!$O) return; // this is the first item, selectLabel
+
+ var $removeLink = $("<a></a>")
+ .attr("href", "#")
+ .addClass(options.removeClass)
+ .prepend(options.removeLabel)
+ .click(function() {
+ dropListItem($(this).parent('li').attr('rel'));
+ return false;
+ });
+
+ var $itemLabel = $("<span></span>")
+ .addClass(options.listItemLabelClass)
+ .html($O.html());
+
+ var $item = $("<li></li>")
+ .attr('rel', optionId)
+ .addClass(options.listItemClass)
+ .append($itemLabel)
+ .append($removeLink)
+ .hide();
+
+ if(!buildingSelect) {
+ if($O.is(":selected")) return; // already have it
+ $O.attr('selected', true);
+ }
+
+ if(options.addItemTarget == 'top' && !buildingSelect) {
+ $ol.prepend($item);
+ if(options.sortable) $original.prepend($O);
+ } else {
+ $ol.append($item);
+ if(options.sortable) $original.append($O);
+ }
+
+ addListItemShow($item);
+
+ disableSelectOption($("[rel=" + optionId + "]", $select));
+
+ if(!buildingSelect) {
+ setHighlight($item, options.highlightAddedLabel);
+ selectFirstItem();
+ if(options.sortable) $ol.sortable("refresh");
+ }
+
+ }
+
+ function addListItemShow($item) {
+
+ // reveal the currently hidden item with optional animation
+ // used only by addListItem()
+
+ if(options.animate && !buildingSelect) {
+ $item.animate({
+ opacity: "show",
+ height: "show"
+ }, 100, "swing", function() {
+ $item.animate({
+ height: "+=2px"
+ }, 50, "swing", function() {
+ $item.animate({
+ height: "-=2px"
+ }, 25, "swing");
+ });
+ });
+ } else {
+ $item.show();
+ }
+ }
+
+ function dropListItem(optionId, highlightItem) {
+
+ // remove an item from the html list
+
+ if(highlightItem == undefined) var highlightItem = true;
+ var $O = $('#' + optionId);
+
+ $O.attr('selected', false);
+ $item = $ol.children("li[rel=" + optionId + "]");
+
+ dropListItemHide($item);
+ enableSelectOption($("[rel=" + optionId + "]", options.removeWhenAdded ? $selectRemoved : $select));
+
+ if(highlightItem) setHighlight($item, options.highlightRemovedLabel);
+
+ triggerOriginalChange(optionId, 'drop');
+
+ }
+
+ function dropListItemHide($item) {
+
+ // remove the currently visible item with optional animation
+ // used only by dropListItem()
+
+ if(options.animate && !buildingSelect) {
+
+ $prevItem = $item.prev("li");
+
+ $item.animate({
+ opacity: "hide",
+ height: "hide"
+ }, 100, "linear", function() {
+ $prevItem.animate({
+ height: "-=2px"
+ }, 50, "swing", function() {
+ $prevItem.animate({
+ height: "+=2px"
+ }, 100, "swing");
+ });
+ $item.remove();
+ });
+
+ } else {
+ $item.remove();
+ }
+ }
+
+ function setHighlight($item, label) {
+
+ // set the contents of the highlight area that appears
+ // directly after the <select> single
+ // fade it in quickly, then fade it out
+
+ if(!options.highlight) return;
+
+ $select.next("#" + options.highlightClass + index).remove();
+
+ var $highlight = $("<span></span>")
+ .hide()
+ .addClass(options.highlightClass)
+ .attr('id', options.highlightClass + index)
+ .html(label + $item.children("." + options.listItemLabelClass).slice(0,1).text());
+
+ $select.after($highlight);
+
+ $highlight.fadeIn("fast", function() {
+ setTimeout(function() { $highlight.fadeOut("slow"); }, 50);
+ });
+ }
+
+ function triggerOriginalChange(optionId, type) {
+
+ // trigger a change event on the original select multiple
+ // so that other scripts can pick them up
+
+ ignoreOriginalChangeEvent = true;
+ $option = $("#" + optionId);
+
+ $original.trigger('change', [{
+ 'option': $option,
+ 'value': $option.val(),
+ 'id': optionId,
+ 'item': $ol.children("[rel=" + optionId + "]"),
+ 'type': type
+ }]);
+ }
+
+ init();
+ });
+ };
+
+})(jQuery);