1320 lines
No EOL
38 KiB
JavaScript
1320 lines
No EOL
38 KiB
JavaScript
/*
|
|
ufd 0.6 : Unobtrusive Fast-filter Drop-down jQuery plugin.
|
|
|
|
Authors:
|
|
thetoolman@gmail.com
|
|
Kadalashvili.Vladimir@gmail.com
|
|
|
|
Version: 0.6
|
|
|
|
Website: http://code.google.com/p/ufd/
|
|
*/
|
|
|
|
(function($) {
|
|
|
|
var widgetName = "ui.ufd";
|
|
|
|
$.widget(widgetName, {
|
|
|
|
// options: provided by framework
|
|
// element: provided by framework
|
|
|
|
_init: function() { //1.7 init
|
|
// in 1.8 this method is "default functionality" which we dont have; here for 1.7 support
|
|
if(!this.created) this._create();
|
|
},
|
|
|
|
_create: function() { //1.8 init
|
|
if(this.created) return;
|
|
this.created = true;
|
|
|
|
if (this.element[0].tagName.toLowerCase() != "select") {
|
|
this.destroy();
|
|
return false;
|
|
}
|
|
|
|
// this._timingMeasure(true, "init");
|
|
|
|
this.options = $.extend(true, {}, this.options); //deep copy: http://dev.jqueryui.com/ticket/4366
|
|
|
|
this.visibleCount = 0;
|
|
this.selectbox = this.element;
|
|
this.logNode = $(this.options.logSelector);
|
|
this.overflowCSS = this.options.allowLR ? "overflow" : "overflowY";
|
|
var selectName = this.selectbox.attr("name");
|
|
var prefixName = this.options.prefix + selectName;
|
|
var inputName = this.options.submitFreeText ? selectName : prefixName;
|
|
var inputId = ""; // none unless master select has one
|
|
|
|
var sbId = this.selectbox.attr("id");
|
|
|
|
if(sbId) {
|
|
inputId = this.options.prefix + sbId ;
|
|
this.labels = $("label[for='" + sbId + "']").attr("for", inputId);
|
|
}
|
|
|
|
if(this.options.submitFreeText) this.selectbox.attr("name", prefixName);
|
|
if(this.options.calculateZIndex) this.options.zIndexPopup = this._calculateZIndex();
|
|
|
|
var css = this.options.css;
|
|
this.css = this.options.css;
|
|
if(this.options.useUiCss) $.extend(this.css, this.options.uiCss);
|
|
if(!css.skin) css.skin = this.options.skin; // use option skin if not specified in CSS
|
|
|
|
this.wrapper = $([
|
|
'<span class="', css.wrapper, ' ', css.hidden, ' ', css.skin, '">',
|
|
'<input type="text" id="',inputId,'" class="', css.input, '" name="', inputName, '"/>',
|
|
'<button type="button" tabindex="-1" class="', css.button, '"><div class="', css.buttonIcon, '"/></button>',
|
|
// <select .../> goes here
|
|
'</span>'
|
|
].join(''));
|
|
this.dropdown = $([
|
|
'<div class="', css.skin, '">',
|
|
'<div class="', css.listWrapper, ' ', css.hidden, '">',
|
|
'<div class="', css.listScroll, '">',
|
|
// <ul/> goes here
|
|
'</div>',
|
|
'</div>',
|
|
'</div>'
|
|
].join(''));
|
|
|
|
this.selectbox.after(this.wrapper);
|
|
this.getDropdownContainer().append(this.dropdown);
|
|
|
|
this.input = this.wrapper.find("input");
|
|
this.button = this.wrapper.find("button");
|
|
this.listWrapper = this.dropdown.children(":first").css("z-index", this.options.zIndexPopup);
|
|
this.listScroll = this.listWrapper.children(":first");
|
|
|
|
if($.fn.bgiframe) this.listWrapper.bgiframe(); //ie6 !
|
|
|
|
// check browser supports min-width, revert to fixed if no support - Looking at you, iE6...
|
|
if(!this.options.listWidthFixed){
|
|
this.listWrapper.css({"width": 50, "min-width": 100});
|
|
this.options.listWidthFixed = (this.listWrapper.width() < 100);
|
|
this.listWrapper.css({"width": null, "min-width": null});
|
|
}
|
|
|
|
this._populateFromMaster();
|
|
this._initEvents();
|
|
|
|
// this._timingMeasure(false, "init");
|
|
},
|
|
|
|
|
|
_initEvents: function() { //initialize all event listeners
|
|
var self = this;
|
|
var keyCodes = $.ui.keyCode;
|
|
var key, isKeyDown, isKeyPress,isKeyUp;
|
|
var css = this.options.css;
|
|
|
|
// this.log("initEvents");
|
|
|
|
this.input.bind("keydown keypress keyup", function(event) {
|
|
// Key handling is tricky; here is great key guide: http://unixpapa.com/js/key.html
|
|
isKeyDown = (event.type == "keydown");
|
|
isKeyPress = (event.type == "keypress");
|
|
isKeyUp = (event.type == "keyup");
|
|
key = null;
|
|
|
|
if (undefined === event.which) {
|
|
key = event.keyCode;
|
|
} else if (!isKeyPress && event.which != 0) {
|
|
key = event.keyCode;
|
|
} else {
|
|
return; //special key
|
|
}
|
|
|
|
switch (key) { //stop default behivour for these events
|
|
case keyCodes.HOME:
|
|
case keyCodes.END:
|
|
if(self.options.homeEndForCursor) return; //no action except default
|
|
case keyCodes.DOWN:
|
|
case keyCodes.PAGE_DOWN:
|
|
case keyCodes.UP:
|
|
case keyCodes.PAGE_UP:
|
|
case keyCodes.ENTER:
|
|
self.stopEvent(event);
|
|
default:
|
|
}
|
|
|
|
// only process: keyups excluding tab/return; and only tab/return keydown
|
|
// Only some browsers fire keyUp on tab in, ignore if it happens
|
|
if(!isKeyUp == ((key != keyCodes.TAB) && (key != keyCodes.ENTER)) ) return;
|
|
|
|
// self.log("Key: " + key + " event: " + event.type);
|
|
|
|
self.lastKey = key;
|
|
|
|
switch (key) {
|
|
case keyCodes.SHIFT:
|
|
case keyCodes.CONTROL:
|
|
//don't refilter
|
|
break;
|
|
|
|
case keyCodes.DOWN:
|
|
self.selectNext(false);
|
|
break;
|
|
case keyCodes.PAGE_DOWN:
|
|
self.selectNext(true);
|
|
break;
|
|
case keyCodes.END:
|
|
self.selectLast();
|
|
break;
|
|
|
|
case keyCodes.UP:
|
|
self.selectPrev(false);
|
|
break;
|
|
case keyCodes.PAGE_UP:
|
|
self.selectPrev(true);
|
|
break;
|
|
case keyCodes.HOME:
|
|
self.selectFirst();
|
|
break;
|
|
|
|
case keyCodes.ENTER:
|
|
self.hideList();
|
|
self.tryToSetMaster();
|
|
self.inputFocus();
|
|
break;
|
|
case keyCodes.TAB: //tabout only
|
|
self.realLooseFocusEvent();
|
|
break;
|
|
case keyCodes.ESCAPE:
|
|
self.hideList();
|
|
self.revertSelected();
|
|
break;
|
|
|
|
default:
|
|
self.showList();
|
|
self.filter(false, true); //do delay, as more keypresses may cancel
|
|
break;
|
|
}
|
|
});
|
|
|
|
this.input.bind("click", function(e) {
|
|
if(self.isDisabled){
|
|
self.stopEvent(e);
|
|
return;
|
|
}
|
|
// self.log("input click: " + e.target);
|
|
if (!self.listVisible()) {
|
|
self.filter(true); //show all
|
|
self.inputFocus();
|
|
self.showList();
|
|
}
|
|
});
|
|
this.input.bind("focus", function(e) {
|
|
if(self.isDisabled){
|
|
self.stopEvent(e);
|
|
return;
|
|
}
|
|
// self.log("input focus");
|
|
if(!self.internalFocus){
|
|
self.realFocusEvent();
|
|
}
|
|
});
|
|
|
|
this.button.bind("mouseover", function(e) { self.button.addClass(css.buttonHover); });
|
|
this.button.bind("mouseout", function(e) { self.button.removeClass(css.buttonHover); });
|
|
this.button.bind("mousedown", function(e) { self.button.addClass(css.buttonMouseDown); });
|
|
this.button.bind("mouseup", function(e) { self.button.removeClass(css.buttonMouseDown); });
|
|
this.button.bind("click", function(e) {
|
|
if(self.isDisabled){
|
|
self.stopEvent(e);
|
|
return;
|
|
}
|
|
// self.log("button click: " + e.target);
|
|
if (self.listVisible()) {
|
|
self.hideList();
|
|
self.inputFocus();
|
|
|
|
} else {
|
|
self.filter(true); //show all
|
|
self.inputFocus();
|
|
self.showList();
|
|
}
|
|
});
|
|
|
|
/*
|
|
* Swallow mouse scroll to prevent body scroll
|
|
* thanks http://www.switchonthecode.com/tutorials/javascript-tutorial-the-scroll-wheel
|
|
*/
|
|
this.listScroll.bind("DOMMouseScroll mousewheel", function(e) {
|
|
self.stopEvent(e);
|
|
e = e ? e : window.event;
|
|
var normal = e.detail ? e.detail * -1 : e.wheelDelta / 40;
|
|
|
|
var curST = self.listScroll.scrollTop();
|
|
var newScroll = curST + ((normal > 0) ? -1 * self.itemHeight : 1 * self.itemHeight);
|
|
self.listScroll.scrollTop(newScroll);
|
|
});
|
|
|
|
this.listScroll.bind("mouseover mouseout click", function(e) {
|
|
if ( "LI" == e.target.nodeName.toUpperCase() ) {
|
|
if(self.setActiveTimeout) { //cancel pending selectLI -> active
|
|
clearTimeout(self.setActiveTimeout);
|
|
self.setActiveTimeout == null;
|
|
}
|
|
if ("mouseout" == e.type) {
|
|
$(e.target).removeClass(css.liActive);
|
|
self.setActiveTimeout = setTimeout(function() {
|
|
$(self.selectedLi).addClass(css.liActive);
|
|
}, self.options.delayYield);
|
|
|
|
} else if ("mouseover" == e.type) {
|
|
if (self.selectedLi != e.target) {
|
|
$(self.selectedLi).removeClass(css.liActive);
|
|
}
|
|
$(e.target).addClass(css.liActive);
|
|
|
|
} else { //click
|
|
self.stopEvent(e); //prevent bubbling to document onclick binding etc
|
|
var value = $.trim($(e.target).text());
|
|
self.input.val(value);
|
|
self.setActive(e.target);
|
|
if(self.tryToSetMaster() ) {
|
|
self.hideList();
|
|
self.filter(true); //show all
|
|
}
|
|
self.inputFocus();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
this.selectbox.bind("change." + widgetName, function(e) {
|
|
if(self.isUpdatingMaster){
|
|
// self.log("master changed but we did the update");
|
|
self.isUpdatingMaster = false;
|
|
return true;
|
|
}
|
|
// self.log("master changed; reverting");
|
|
self.revertSelected();
|
|
});
|
|
|
|
// click anywhere else; keep reference for selective unbind
|
|
this._myDocClickHandler = function(e) {
|
|
if ((self.button.get(0) == e.target) || (self.input.get(0) == e.target)) return;
|
|
// self.log("unfocus document click : " + e.target);
|
|
if (self.internalFocus) self.realLooseFocusEvent();
|
|
};
|
|
$(document).bind("click." + widgetName, this._myDocClickHandler);
|
|
|
|
// polling for disabled, dimensioned
|
|
if(this.options.polling) {
|
|
var self = this; // shadow self var - less lookup chain == faster
|
|
this._myPollId = setInterval(function() {
|
|
// fast as possible
|
|
if(!self.dimensioned) self.setDimensions();
|
|
if(self.selectbox[0].disabled != self.isDisabled) {
|
|
(self.selectbox[0].disabled) ? self.disable() : self.enable();
|
|
}
|
|
|
|
}, self.options.polling);
|
|
}
|
|
|
|
},
|
|
|
|
// pseudo events
|
|
|
|
realFocusEvent: function() {
|
|
// this.log("real input focus");
|
|
this.internalFocus = true;
|
|
this._triggerEventOnMaster("focus");
|
|
this.wrapper.addClass(this.options.css.skin + "-" + this.options.css.inputFocus); // for ie6 support
|
|
this.input.addClass(this.options.css.inputFocus);
|
|
this.button.addClass(this.options.css.inputFocus);
|
|
this.filter(true); //show all
|
|
this.inputFocus();
|
|
this.showList();
|
|
},
|
|
|
|
realLooseFocusEvent: function() {
|
|
// this.log("real loose focus (blur)");
|
|
this.internalFocus = false;
|
|
this.hideList();
|
|
this.wrapper.removeClass(this.options.css.skin + "-" + this.options.css.inputFocus);
|
|
this.input.removeClass(this.options.css.inputFocus);
|
|
this.button.removeClass(this.options.css.inputFocus);
|
|
this.tryToSetMaster();
|
|
this._triggerEventOnMaster("blur");
|
|
},
|
|
|
|
_triggerEventOnMaster: function(eventName) {
|
|
if( document.createEvent ) { // good browsers
|
|
var evObj = document.createEvent('HTMLEvents');
|
|
evObj.initEvent( eventName, true, true );
|
|
this.selectbox.get(0).dispatchEvent(evObj);
|
|
|
|
} else if( document.createEventObject ) { // iE
|
|
this.selectbox.get(0).fireEvent("on" + eventName);
|
|
}
|
|
|
|
},
|
|
|
|
// methods
|
|
|
|
inputFocus: function() {
|
|
// this.log("inputFocus: restore input component focus");
|
|
this.input.focus();
|
|
|
|
if (this.getCurrentTextValue().length) {
|
|
this.selectAll();
|
|
}
|
|
},
|
|
|
|
inputBlur: function() {
|
|
// this.log("inputBlur: loose input component focus");
|
|
this.input.blur();
|
|
},
|
|
|
|
showList: function() {
|
|
// this.log("showlist");
|
|
if(this.listVisible()) return;
|
|
this.listWrapper.removeClass(this.css.hidden);
|
|
this.setListDisplay();
|
|
},
|
|
|
|
hideList: function() {
|
|
// this.log("hide list");
|
|
if(!this.listVisible()) return;
|
|
this.listWrapper.addClass(this.css.hidden);
|
|
this.listItems.removeClass(this.css.hidden);
|
|
},
|
|
|
|
/*
|
|
* adds / removes items to / from the dropdown list depending on combo's current value
|
|
*
|
|
* if doDelay, will delay execution to allow re-entry to cancel.
|
|
*/
|
|
filter: function(showAll, doDelay) {
|
|
// this.log("filter: " );
|
|
var self = this;
|
|
|
|
//cancel any pending
|
|
if(this.updateOnTimeout) clearTimeout(this.updateOnTimeout);
|
|
if(this.filterOnTimeout) clearTimeout(this.filterOnTimeout);
|
|
this.updateOnTimeout = null;
|
|
this.filterOnTimeout = null;
|
|
|
|
var searchText = self.getCurrentTextValue();
|
|
|
|
var search = function() {
|
|
// self.log("filter search");
|
|
// this._timingMeasure(true, "filter search");
|
|
var mm = self.trie.find(searchText); // search!
|
|
self.trie.matches = mm.matches;
|
|
self.trie.misses = mm.misses;
|
|
// this._timingMeasure(false, "filter search");
|
|
|
|
//yield then screen update
|
|
self.updateOnTimeout = setTimeout(function(){screenUpdate();}, self.options.delayYield);
|
|
|
|
};
|
|
|
|
var screenUpdate = function() {
|
|
// self.log("screen update");
|
|
|
|
// this._timingMeasure(true, "screenUpdate");
|
|
var active = self.getActive(); //get item before class-overwrite
|
|
|
|
if (self.options.addEmphasis) {
|
|
self.emphasis(self.trie.matches, true, searchText);
|
|
}
|
|
|
|
self.visibleCount = self.overwriteClass(self.trie.matches, "" );
|
|
if(showAll || !self.trie.matches.length) {
|
|
self.visibleCount += self.overwriteClass(self.trie.misses, "" );
|
|
if (self.options.addEmphasis) {
|
|
self.emphasis(self.trie.misses, false, searchText);
|
|
}
|
|
} else {
|
|
self.overwriteClass(self.trie.misses, self.css.hidden);
|
|
}
|
|
var oldActiveHidden = active.hasClass(self.css.hidden) ;
|
|
|
|
// need to set overwritten active class
|
|
if(!oldActiveHidden && active.length && self.trie.matches.length){
|
|
self.setActive(active.get(0));
|
|
|
|
} else {
|
|
var firstmatch = self.listItems.filter(":visible:first");
|
|
self.setActive(firstmatch.get(0));
|
|
|
|
}
|
|
// this._timingMeasure(false, "screenUpdate");
|
|
|
|
|
|
self.setListDisplay();
|
|
};
|
|
|
|
if(doDelay) {
|
|
//setup new delay
|
|
this.filterOnTimeout = setTimeout( function(){ search(); }, this.options.delayFilter );
|
|
} else {
|
|
search();
|
|
}
|
|
},
|
|
|
|
/*
|
|
* replace chars with entity encoding
|
|
*/
|
|
_encodeDom: $('<div/>'),
|
|
_encodeString: function(toEnc) {
|
|
return $.trim(this._encodeDom.text(toEnc).html());
|
|
},
|
|
|
|
emphasis: function(array, isAddEmphasis, searchText ) {
|
|
|
|
var tritem, index, indexB, li, text, stPattern, escapedST;
|
|
var searchTextLength = searchText.length || 0;
|
|
var options = this.selectbox.get(0).options;
|
|
index = array.length;
|
|
|
|
isAddEmphasis = (isAddEmphasis && searchTextLength > 0); // don't add emphasis to 0-length
|
|
|
|
if(isAddEmphasis) {
|
|
// html encode search string to match innerHTML then escape regexp chars; thanks http://xkr.us/js/regexregex
|
|
escapedST = this._encodeString(searchText).replace(/([\\\^\$*+[\]?{}.=!:(|)])/g,"\\$1");
|
|
stPattern = new RegExp("(" + escapedST + ")", "gi"); // $1
|
|
this.hasEmphasis = true;
|
|
}
|
|
// this.log("add emphasis? " + isAddEmphasis);
|
|
// this._timingMeasure(true, "em");
|
|
while(index--) {
|
|
tritem = array[index];
|
|
indexB = tritem.length;
|
|
while(indexB--) { // duplicate match array
|
|
li = tritem[indexB];
|
|
text = $.trim(options[li.getAttribute("name")].innerHTML);
|
|
li.innerHTML = isAddEmphasis ? text.replace(stPattern, "<em>$1</em>") : text;
|
|
}
|
|
}
|
|
// this._timingMeasure(false, "em");
|
|
},
|
|
|
|
/*
|
|
_timingMeasure_agnostic : function(label, isStart) {
|
|
if(isStart) {
|
|
this._tm[label] = new Date().getTime();
|
|
} else {
|
|
var start = this._tm[label];
|
|
var dur = (new Date().getTime()) - start;
|
|
alert(label + ": millis - " + dur);
|
|
}
|
|
},
|
|
_tm : {},
|
|
*/
|
|
_timingMeasure_firebug : function(isStart, label) {
|
|
if(isStart) {
|
|
console.time(label);
|
|
} else {
|
|
console.timeEnd(label);
|
|
}
|
|
},
|
|
_timingMeasure : function(isStart, label) {
|
|
this._timingMeasure_firebug(isStart, label);
|
|
},
|
|
removeEmphasis : function() {
|
|
// this.log("remove emphasis");
|
|
if(!this.hasEmphasis){
|
|
// this.log("no emphasis to remove");
|
|
return;
|
|
}
|
|
// this._timingMeasure(true, "rem");
|
|
this.hasEmphasis = false;
|
|
var options = this.selectbox.get(0).options;
|
|
var theLiSet = this.list.get(0).getElementsByTagName('LI'); // much faster array then .childElements !
|
|
var liCount = theLiSet.length;
|
|
var li;
|
|
while(liCount--){
|
|
var li = theLiSet[liCount];
|
|
li.innerHTML = $.trim(options[li.getAttribute("name")].innerHTML);
|
|
}
|
|
|
|
|
|
// this._timingMeasure(false, "rem");
|
|
|
|
},
|
|
|
|
// attempt update of master - returns true if update good or already set correct.
|
|
tryToSetMaster: function() {
|
|
// this.log("t.s.m");
|
|
|
|
var optionIndex = null;
|
|
var active = this.getActive();
|
|
if (active.length) {
|
|
optionIndex = active.attr("name"); //sBox pointer index
|
|
}
|
|
if (optionIndex == null || optionIndex == "" || optionIndex < 0) {
|
|
// this.log("no active, master not set.");
|
|
if (this.options.submitFreeText) {
|
|
return false;
|
|
|
|
} else {
|
|
// this.log("Not freetext and no active set; revert.");
|
|
this.revertSelected();
|
|
return false;
|
|
}
|
|
} // else optionIndex is set to activeIndex
|
|
|
|
var sBox = this.selectbox.get(0);
|
|
var curIndex = sBox.selectedIndex;
|
|
var option = sBox.options[optionIndex];
|
|
|
|
if(!this.options.submitFreeText || this.input.val() == option.text){ //freetext only if exact match
|
|
this.input.val(option.text); // input may be only partially set
|
|
|
|
if(optionIndex != curIndex){
|
|
this.isUpdatingMaster = true;
|
|
sBox.selectedIndex = optionIndex;
|
|
// this.log("master selectbox set to: " + option.text);
|
|
this._triggerEventOnMaster("change");
|
|
|
|
} // else already correctly set, no change
|
|
return true;
|
|
|
|
} // else have a non-matched freetext
|
|
// this.log("unmatched freetext, master not set.");
|
|
|
|
return false;
|
|
},
|
|
|
|
_populateFromMaster: function() {
|
|
// this.log("populate from master select");
|
|
// this._timingMeasure(true, "prep");
|
|
var isEnabled = !this.selectbox.filter("[disabled]").length; //remember incoming state
|
|
this.disable();
|
|
|
|
this.trie = new InfixTrie(this.options.infix, this.options.caseSensitive);
|
|
this.trie.matches = [];
|
|
this.trie.misses = [];
|
|
|
|
var self = this;
|
|
var listBuilder = [];
|
|
|
|
// this._timingMeasure(false, "prep");
|
|
// this._timingMeasure(true, "build");
|
|
|
|
listBuilder.push('<ul>');
|
|
var options = this.selectbox.get(0).options;
|
|
var thisOpt,loopCountdown,index;
|
|
|
|
loopCountdown = options.length;
|
|
// this.log("loopCountDown: " + loopCountdown);
|
|
index = 0;
|
|
while(loopCountdown--) {
|
|
thisOpt = options[index++];
|
|
listBuilder.push('<li name="');
|
|
listBuilder.push(thisOpt.index);
|
|
listBuilder.push('">');
|
|
listBuilder.push($.trim(thisOpt.innerHTML));
|
|
listBuilder.push('</li>');
|
|
}
|
|
|
|
listBuilder.push('</ul>');
|
|
|
|
this.listScroll.html(listBuilder.join(''));
|
|
this.list = this.listScroll.find("ul:first");
|
|
|
|
// this._timingMeasure(false, "build");
|
|
|
|
// this._timingMeasure(true, "kids");
|
|
var theLiSet = this.list.get(0).getElementsByTagName('LI'); // much faster array then .childElements !
|
|
this.listItems = $(theLiSet);
|
|
|
|
loopCountdown = theLiSet.length;
|
|
index = 0;
|
|
while(loopCountdown--) {
|
|
thisOpt = options[index];
|
|
self.trie.add( $.trim(thisOpt.text), theLiSet[index++]); //option.text not innerHTML for trie as we dont want escaping
|
|
}
|
|
|
|
// this._timingMeasure(false, "kids");
|
|
// this._timingMeasure(true, "tidy");
|
|
|
|
this.visibleCount = theLiSet.length;
|
|
this.setInputFromMaster();
|
|
this.selectedLi = null;
|
|
|
|
this.dimensioned = false;
|
|
this.setDimensions();
|
|
|
|
if(isEnabled) this.enable();
|
|
// this._timingMeasure(false, "tidy");
|
|
|
|
this._moveAttrs(this.selectbox, this.input, this.options.moveAttrs);
|
|
},
|
|
|
|
/*
|
|
* cuts and pastes all listed attributes from the source to the destination
|
|
*/
|
|
_moveAttrs: function(src, dest, attrs) {
|
|
|
|
for (var i = 0; i < attrs.length; ++i) {
|
|
var attr = attrs[i];
|
|
var value = src.attr(attr);
|
|
if (value) {
|
|
dest.attr(attr, value);
|
|
src.removeAttr(attr);
|
|
}
|
|
}
|
|
|
|
},
|
|
|
|
/*
|
|
* This method is called by the poller, so needs to return quickly when not dimensioning
|
|
*/
|
|
setDimensions: function() {
|
|
// if a new UFD (unwrapped) and selectbox is invisible, we cant dimension
|
|
if(!this.selectIsWrapped && !this.selectbox.filter(":visible").length) {
|
|
return;
|
|
}
|
|
// if pre-exising UFD needs redimensioning, but is not visible
|
|
if(this.selectIsWrapped && !this.wrapper.filter(":visible").length){
|
|
return;
|
|
}
|
|
|
|
this.wrapper.addClass(this.css.hidden);
|
|
if(this.selectIsWrapped && (!this.options.manualWidth || this.options.unwrapForCSS)) { // unwrap
|
|
this.wrapper.before(this.selectbox);
|
|
this.selectIsWrapped = false;
|
|
}
|
|
|
|
//match original width
|
|
var newSelectWidth;
|
|
if(this.options.manualWidth) {
|
|
newSelectWidth = this.options.manualWidth;
|
|
} else {
|
|
newSelectWidth = this.selectbox.outerWidth();
|
|
if (newSelectWidth < this.options.minWidth) {
|
|
newSelectWidth = this.options.minWidth;
|
|
} else if (this.options.maxWidth && (newSelectWidth > this.options.maxWidth) ) {
|
|
newSelectWidth = this.options.maxWidth;
|
|
}
|
|
}
|
|
|
|
var props = this.options.mimicCSS;
|
|
for(propPtr in props){
|
|
var prop = props[propPtr];
|
|
if(!props.hasOwnProperty(propPtr) || typeof prop === 'function') continue;
|
|
this.wrapper.css(prop, this.selectbox.css(prop)); // copy property from selectbox to wrapper
|
|
}
|
|
|
|
if(!this.selectIsWrapped) { // wrap
|
|
this.wrapper.get(0).appendChild(this.selectbox.get(0));
|
|
this.selectIsWrapped = true;
|
|
}
|
|
|
|
this.wrapper.removeClass(this.css.hidden);
|
|
this.listWrapper.removeClass(this.css.hidden);
|
|
|
|
var buttonWidth = this.button.outerWidth(true);
|
|
var wrapperBP = this.wrapper.outerWidth() - this.wrapper.width();
|
|
var inputBP = this.input.outerWidth(true) - this.input.width();
|
|
var listScrollBP = this.listScroll.outerWidth() - this.listScroll.width();
|
|
var inputWidth = newSelectWidth - buttonWidth - inputBP;
|
|
|
|
this.input.width(inputWidth);
|
|
this.wrapper.width(newSelectWidth);
|
|
|
|
var cssWidth = this.options.listWidthFixed ? "width" : "min-width";
|
|
this.listWrapper.css(cssWidth, newSelectWidth + wrapperBP);
|
|
this.listScroll.css(cssWidth, newSelectWidth + wrapperBP - listScrollBP);
|
|
|
|
/* console.log(newSelectWidth + " : " + inputWidth + " : " +
|
|
buttonWidth + " : " + listScrollBP + " : " + wrapperBP); */
|
|
this.listWrapper.addClass(this.css.hidden);
|
|
|
|
this.dimensioned = true;
|
|
|
|
},
|
|
|
|
setInputFromMaster: function() {
|
|
var selectNode = this.selectbox.get(0);
|
|
var val = "";
|
|
try {
|
|
val = selectNode.options[selectNode.selectedIndex].text;
|
|
} catch(e) {
|
|
//must have no items!BP
|
|
}
|
|
//this.log("setting input to: " + val);
|
|
this.input.val(val);
|
|
},
|
|
|
|
revertSelected: function() {
|
|
this.setInputFromMaster();
|
|
this.filter(true); //show all
|
|
},
|
|
|
|
//corrects list wrapper's height depending on list items height
|
|
setListDisplay: function() {
|
|
|
|
// this._timingMeasure(true, "listDisplay");
|
|
if(!this.itemHeight) { // caclulate only once
|
|
this.itemHeight = this.listItems.filter("li:first").outerHeight(true);
|
|
// this.log("listItemHeight: " + this.itemHeight);
|
|
}
|
|
var height;
|
|
|
|
if (this.visibleCount > this.options.listMaxVisible) {
|
|
height = this.options.listMaxVisible * this.itemHeight;
|
|
this.listScroll.css(this.overflowCSS, "scroll");
|
|
} else {
|
|
height = this.visibleCount * this.itemHeight;
|
|
this.listScroll.css(this.overflowCSS, "hidden");
|
|
}
|
|
|
|
// this.log("height set to: " + height);
|
|
this.listScroll.height(height);
|
|
var outerHeight = this.listScroll.outerHeight();
|
|
this.listWrapper.height(outerHeight);
|
|
|
|
//height set, now position
|
|
|
|
var offset = this.wrapper.offset();
|
|
var wrapperOuterHeight = this.wrapper.outerHeight();
|
|
var bottomPos = offset.top + wrapperOuterHeight + outerHeight;
|
|
|
|
var maxShown = $(window).height() + $(document).scrollTop();
|
|
var doDropUp = (bottomPos > maxShown);
|
|
|
|
|
|
var left = offset.left;
|
|
var top;
|
|
|
|
if (doDropUp) {
|
|
this.listWrapper.addClass(this.css.listWrapperUp);
|
|
top = (offset.top - outerHeight) ;
|
|
} else {
|
|
this.listWrapper.removeClass(this.css.listWrapperUp);
|
|
top = (offset.top + wrapperOuterHeight);
|
|
}
|
|
this.listWrapper.css("left", left);
|
|
this.listWrapper.css("top", top );
|
|
this.scrollTo();
|
|
|
|
// this._timingMeasure(false, "listDisplay");
|
|
|
|
return height;
|
|
},
|
|
|
|
//returns active (hovered) element of the dropdown list
|
|
getActive: function() {
|
|
// this.log("get active");
|
|
if(this.selectedLi == null) return $([]);
|
|
return $(this.selectedLi);
|
|
},
|
|
|
|
//highlights the item given
|
|
setActive: function(activeItem) {
|
|
// this.log("setActive");
|
|
$(this.selectedLi).removeClass(this.css.liActive);
|
|
this.selectedLi = activeItem;
|
|
$(this.selectedLi).addClass(this.css.liActive);
|
|
},
|
|
|
|
selectFirst: function() {
|
|
// this.log("selectFirst");
|
|
var toSelect = this.listItems.filter(":not(.invisible):first");
|
|
this.afterSelect( toSelect );
|
|
},
|
|
|
|
selectLast: function() {
|
|
// this.log("selectFirst");
|
|
var toSelect = this.listItems.filter(":not(.invisible):last");
|
|
this.afterSelect( toSelect );
|
|
},
|
|
|
|
|
|
//highlights list item before currently active item
|
|
selectPrev: function(isPageLength) {
|
|
// this.log("hilightprev");
|
|
var count = isPageLength ? this.options.pageLength : 1;
|
|
var toSelect = this.searchRelativeVisible(false, count);
|
|
this.afterSelect( toSelect );
|
|
},
|
|
|
|
//highlights item of the dropdown list next to the currently active item
|
|
selectNext: function(isPageLength) {
|
|
// this.log("hilightnext");
|
|
var count = isPageLength? this.options.pageLength : 1;
|
|
var toSelect = this.searchRelativeVisible(true, count);
|
|
this.afterSelect( toSelect );
|
|
},
|
|
|
|
afterSelect: function(active) {
|
|
if(active == null) return;
|
|
this.setActive(active);
|
|
this.input.val(active.text());
|
|
this.scrollTo();
|
|
this.tryToSetMaster();
|
|
this.inputFocus();
|
|
this.removeEmphasis();
|
|
},
|
|
|
|
searchRelativeVisible: function(isSearchDown, count) {
|
|
// this.log("searchRelative: " + isSearchDown + " : " + count);
|
|
|
|
var active = this.getActive();
|
|
if (!active.length) {
|
|
this.selectFirst();
|
|
return null;
|
|
}
|
|
|
|
var searchResult;
|
|
|
|
do { // count times
|
|
searchResult = active;
|
|
do { //find next/prev item
|
|
searchResult = isSearchDown ? searchResult.next() : searchResult.prev();
|
|
} while (searchResult.length && searchResult.hasClass(this.css.hidden));
|
|
|
|
if (searchResult.length) active = searchResult;
|
|
} while(--count);
|
|
|
|
return active;
|
|
},
|
|
|
|
//scrolls list wrapper to active: true if scroll occured
|
|
scrollTo: function() {
|
|
// this.log("scrollTo");
|
|
if ("scroll" != this.listScroll.css(this.overflowCSS)) return false;
|
|
var active = this.getActive();
|
|
if(!active.length) return false;
|
|
|
|
var activePos = Math.floor(active.position().top);
|
|
var activeHeight = active.outerHeight(true);
|
|
var listHeight = this.listWrapper.height();
|
|
var scrollTop = this.listScroll.scrollTop();
|
|
|
|
/* this.log(" AP: " + activePos + " AH: " + activeHeight +
|
|
" LH: " + listHeight + " ST: " + scrollTop); */
|
|
|
|
var top;
|
|
var viewAheadGap = (this.options.viewAhead * activeHeight);
|
|
|
|
if (activePos < viewAheadGap) { // off top
|
|
top = scrollTop + activePos - viewAheadGap;
|
|
} else if( (activePos + activeHeight) >= (listHeight - viewAheadGap) ) { // off bottom
|
|
top = scrollTop + activePos - listHeight + activeHeight + viewAheadGap;
|
|
}
|
|
else return false; // no need to scroll
|
|
// this.log("top: " + top);
|
|
this.listScroll.scrollTop(top);
|
|
return true; // we did scroll.
|
|
},
|
|
|
|
getCurrentTextValue: function() {
|
|
var input = $.trim(this.input.val());
|
|
// this.log("Using input value: " + input);
|
|
return input;
|
|
},
|
|
|
|
stopEvent: function(e) {
|
|
e = e ? e : window.event;
|
|
e.cancel = true;
|
|
e.cancelBubble = true;
|
|
e.returnValue = false;
|
|
if (e.stopPropagation) {e.stopPropagation(); }
|
|
if( e.preventDefault ) { e.preventDefault(); }
|
|
},
|
|
|
|
overwriteClass: function(array, classString ) { //fast attribute OVERWRITE
|
|
// this._timingMeasure(true, "overwriteClass");
|
|
var tritem, index, indexB, count = 0;
|
|
index = array.length;
|
|
while(index--) {
|
|
tritem = array[index];
|
|
indexB = tritem.length;
|
|
count += indexB;
|
|
while(indexB--) { // duplicate match array
|
|
tritem[indexB].setAttribute($.ui.ufd.classAttr, classString);
|
|
}
|
|
}
|
|
// this._timingMeasure(false, "overwriteClass");
|
|
return count;
|
|
},
|
|
|
|
listVisible: function() {
|
|
var isVisible = !this.listWrapper.hasClass(this.css.hidden);
|
|
// this.log("is list visible?: " + isVisible);
|
|
return isVisible;
|
|
},
|
|
|
|
disable: function() {
|
|
// this.log("disable");
|
|
|
|
this.hideList();
|
|
this.isDisabled = true;
|
|
this.button.addClass(this.css.buttonDisabled);
|
|
this.input.addClass(this.css.inputDisabled);
|
|
this.input.attr("disabled", "disabled");
|
|
this.selectbox.attr("disabled", "disabled");
|
|
},
|
|
|
|
enable: function() {
|
|
// this.log("enable");
|
|
|
|
this.isDisabled = false;
|
|
this.button.removeClass(this.css.buttonDisabled);
|
|
this.input.removeClass(this.css.inputDisabled);
|
|
this.input.removeAttr("disabled");
|
|
this.selectbox.removeAttr("disabled");
|
|
},
|
|
|
|
selectAll: function() {
|
|
// this.log("Select All");
|
|
this.input.get(0).select();
|
|
},
|
|
|
|
getDropdownContainer: function() {
|
|
var ddc = $("#" + this.options.dropDownID);
|
|
if(!ddc.length) { //create
|
|
ddc = $("<div></div>")
|
|
.appendTo("body")
|
|
.css("height", 0)
|
|
.attr("id", this.options.dropDownID);
|
|
}
|
|
return ddc;
|
|
},
|
|
|
|
log: function(msg) {
|
|
if(!this.options.log) return;
|
|
|
|
if(window.console && window.console.log) { // firebug logger
|
|
console.log(msg);
|
|
}
|
|
if( this.logNode && this.logNode.length) {
|
|
this.logNode.prepend("<div>" + msg + "</div>");
|
|
}
|
|
},
|
|
|
|
_calculateZIndex: function(msg) {
|
|
var curZ, zIndex = this.options.zIndexPopup; // start here as a min
|
|
|
|
this.selectbox.parents().each(function(){
|
|
curZ = parseInt($(this).css("zIndex"), 10);
|
|
if(curZ > zIndex) zIndex = curZ;
|
|
});
|
|
return zIndex + 1;
|
|
},
|
|
|
|
changeOptions: function() {
|
|
// this.log("changeOptions");
|
|
this._populateFromMaster();
|
|
},
|
|
|
|
destroy: function() {
|
|
// this.log("called destroy");
|
|
|
|
if(this.selectIsWrapped) { //unwrap
|
|
this.wrapper.before(this.selectbox);
|
|
}
|
|
|
|
this._moveAttrs(this.input, this.selectbox, this.options.moveAttrs); // restore moved attributes
|
|
this.labels.attr("for", this.selectbox.attr("id")); //revert label 'for' attributes.
|
|
this.labels = null;
|
|
|
|
this.selectbox.unbind("change." + widgetName);
|
|
$(document).unbind("click." + widgetName, this._myDocClickHandler);
|
|
if(this._myPollId) clearInterval(this._myPollId );
|
|
//all other handlers are in these removed nodes.
|
|
this.wrapper.remove();
|
|
this.listWrapper.remove();
|
|
|
|
if($.ui.version < "1.8") {
|
|
// see ticket; http://dev.jqueryui.com/ticket/5005 - wasn't fixed 1.7.3
|
|
this.selectbox.unbind("setData." + widgetName);
|
|
this.selectbox.unbind("getData." + widgetName);
|
|
// will remove all events sorry, might have other side effects but needed
|
|
this.selectbox.unbind("remove");
|
|
|
|
$.widget.prototype.destroy.apply(this, arguments); // default destroy
|
|
|
|
} else { // 1.8+
|
|
$.Widget.prototype.destroy.apply(this, arguments); // default destroy
|
|
}
|
|
this.selectbox = null;
|
|
this._encodeDom = null;
|
|
|
|
},
|
|
|
|
|
|
//internal state
|
|
dimensioned: false, // polling flag indicating that setDimensions needs to be called.
|
|
selectIsWrapped: false,
|
|
internalFocus: false,
|
|
lastKey: null,
|
|
selectedLi: null,
|
|
isUpdatingMaster: false,
|
|
created: false,
|
|
hasEmphasis: false,
|
|
isDisabled: false
|
|
|
|
});
|
|
|
|
/****************************************************************************
|
|
* Trie + infix extension implementation for fast prefix or infix searching
|
|
* http://en.wikipedia.org/wiki/Trie
|
|
****************************************************************************/
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
var InfixTrie = function(isInfix, isCaseSensitive){
|
|
|
|
this.isInfix = !!isInfix;
|
|
this.isCaseSensitive = !!isCaseSensitive;
|
|
this.root = [null, {}, false]; //masterNode: object, char -> trieNode map, traverseToggle
|
|
this.infixRoots = (isInfix) ? {} : null;
|
|
};
|
|
|
|
/**
|
|
* Add (String, Object) to store
|
|
*/
|
|
InfixTrie.prototype.add = function( key, object ) {
|
|
key = this.cleanString(key);
|
|
|
|
var kLen = key.length;
|
|
var curNode = this.root;
|
|
var chr, node;
|
|
|
|
for(var i = 0; i < kLen; i++) {
|
|
chr = key.charAt(i);
|
|
node = curNode[1];
|
|
if(chr in node) {
|
|
curNode = node[chr];
|
|
} else {
|
|
curNode = node[chr] = [null, {}, this.root[2]]; // match roots' toggle setting
|
|
|
|
if(this.isInfix) { // only add curNodes once, when created.
|
|
if(chr in this.infixRoots) {
|
|
this.infixRoots[chr].push(curNode);
|
|
} else {
|
|
this.infixRoots[chr] = [curNode];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(curNode[0]) curNode[0].push(object);
|
|
else curNode[0] = [object];
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Get object with two properties:
|
|
* matches: array of all objects not matching entire key (String)
|
|
* misses: array of all objects exactly matching the key (String)
|
|
*
|
|
*/
|
|
InfixTrie.prototype.find = function(key) { // string
|
|
var trieNodeArray = this.findNodeArray(key);
|
|
var toggleTo = !this.root[2];
|
|
var matches = [];
|
|
var misses = [];
|
|
var trie;
|
|
|
|
for(arrName in trieNodeArray){
|
|
trie = trieNodeArray[arrName];
|
|
if(!trieNodeArray.hasOwnProperty(arrName) || typeof trie === 'function') continue;
|
|
|
|
this.markAndRetrieve(matches, trie, toggleTo);
|
|
}
|
|
this.markAndRetrieve(misses, this.root, toggleTo); //will ensure whole tree is toggled.
|
|
|
|
return { matches : matches, misses : misses };
|
|
}
|
|
|
|
/**
|
|
* Find array of trieNodes that match the infix key
|
|
*/
|
|
InfixTrie.prototype.findNodeArray = function(key) {
|
|
var key = this.cleanString(key);
|
|
var retArray = [this.root];
|
|
var kLen = key.length;
|
|
var chr;
|
|
|
|
this.cache = this.cache || {};
|
|
var thisCache = this.cache;
|
|
|
|
for (var i = 0; i < kLen; i++) {
|
|
chr = key.charAt(i);
|
|
if(thisCache.chr == chr) {
|
|
retArray = thisCache.hit;
|
|
|
|
} else {
|
|
retArray = this.mapNewArray(retArray, chr);
|
|
thisCache.chr = chr;
|
|
thisCache.hit = retArray;
|
|
thisCache.next = {};
|
|
}
|
|
thisCache = thisCache.next;
|
|
}
|
|
|
|
return retArray;
|
|
};
|
|
|
|
/**
|
|
* Take an array of nodes, and construct new array of children nodes along the given chr.
|
|
*/
|
|
InfixTrie.prototype.mapNewArray = function(nodeArr, chr) {
|
|
|
|
if(nodeArr.length && nodeArr[0] == this.root) {
|
|
if(this.isInfix) {
|
|
return (this.infixRoots[chr] || []); // return empty array if undefined
|
|
} else {
|
|
var prefixRoot = this.root[1][chr];
|
|
return (prefixRoot) ? [prefixRoot] : [];
|
|
}
|
|
}
|
|
|
|
var retArray = [];
|
|
var aLen = nodeArr.length;
|
|
var thisNodesArray;
|
|
for (var i = 0; i < aLen; i++) {
|
|
thisNodesArray = nodeArr[i][1];
|
|
if(thisNodesArray.hasOwnProperty(chr)){
|
|
retArray.push(thisNodesArray[chr]);
|
|
}
|
|
}
|
|
|
|
return retArray;
|
|
};
|
|
|
|
/**
|
|
* retrieves objects on the given array of trieNodes.
|
|
* Also sets toggleSet and doesnt traverse already marked branches.
|
|
* You must call this with root to ensure complete tree is toggled.
|
|
*/
|
|
InfixTrie.prototype.markAndRetrieve = function(array, trie, toggleSet) {
|
|
var stack = [ trie ];
|
|
while (stack.length > 0) {
|
|
var thisTrie = stack.pop();
|
|
if (thisTrie[2] == toggleSet) continue; //already traversed
|
|
thisTrie[2] = toggleSet;
|
|
if (thisTrie[0]) array.unshift(thisTrie[0]);
|
|
for (chr in thisTrie[1]) {
|
|
if (thisTrie[1].hasOwnProperty(chr)) {
|
|
stack.push(thisTrie[1][chr]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Conform case as needed. Clean invalid characters ?
|
|
*/
|
|
InfixTrie.prototype.cleanString = function( inStr ) {
|
|
if(!this.isCaseSensitive){
|
|
inStr = inStr.toLowerCase();
|
|
}
|
|
//invalid char clean here
|
|
return inStr;
|
|
}
|
|
|
|
/**
|
|
* Expose for testing
|
|
*/
|
|
$.ui.ufd.getNewTrie = function(isCaseSensitive, isInfix){
|
|
return new InfixTrie(isCaseSensitive, isInfix);
|
|
}
|
|
|
|
/* end InfixTrie */
|
|
|
|
|
|
|
|
|
|
$.extend($.ui.ufd, {
|
|
version: "0.6",
|
|
getter: "", //for methods that are getters, not chainables
|
|
classAttr: (($.support.style) ? "class" : "className"), // IE6/7 class attribute
|
|
|
|
defaults: { // 1.7 default options location, see below
|
|
skin: "plain", // skin name
|
|
prefix: "ufd-", // prefix for pseudo-dropdown text input name attr.
|
|
dropDownID: "ufd-container", // ID for a root-child node for storing dropdown lists. avoids ie6 zindex issues by being at top of tree.
|
|
logSelector: "#log", // selector string to write log into, if present.
|
|
mimicCSS: ["float", "tabindex", "marginLeft","marginTop","marginRight","marginBottom"], //copy these properties to widget. Width auto-copied unless min/manual.
|
|
moveAttrs: ["tabindex", "title"], // attributes to move from select to text input
|
|
|
|
|
|
infix: true, //infix search, not prefix
|
|
addEmphasis: false, // add <EM> tags around matches.
|
|
caseSensitive: false, // case sensitive search
|
|
submitFreeText: false, // re[name] original select, give text input the selects' original [name], and allow unmatched entries
|
|
homeEndForCursor: false, // should home/end affect dropdown or move cursor?
|
|
allowLR: false, // show horizontal scrollbar
|
|
calculateZIndex: false, // {max ancestor} + 1
|
|
useUiCss: false, // use jquery UI themeroller classes.
|
|
log: false, // log to firebug console (if available) and logSelector (if it exists)
|
|
unwrapForCSS: false, // unwrap select on reload to get % right on units etc. unwrap causes flicker on reload in iE6
|
|
listWidthFixed: true, // List width matches widget? If false, list can be wider to fit item width, but uses min-width so no iE6 support.
|
|
|
|
polling: 250, // poll msec to test disabled, dimensioned state of master. 0 to disable polling, but needed for (initially) hidden fields.
|
|
listMaxVisible: 10, // number of visible items
|
|
minWidth: 50, // don't autosize smaller then this.
|
|
maxWidth: null, // null, or don't autosize larger then this.
|
|
manualWidth: null, //override selectbox width; set explicit width - stops flicker on reload in iE6 (unless unwrapForCSS) as no unwrap needed
|
|
viewAhead: 1, // items ahead to keep in view when cursor scrolling
|
|
pageLength: 10, // number of visible items jumped on pgup/pgdown.
|
|
delayFilter: ($.support.style) ? 1 : 150, // msec to wait before starting filter (or get cancelled); long for IE
|
|
delayYield: 1, // msec to yield for 2nd 1/2 of filter re-entry cancel; 1 seems adequate to achieve yield
|
|
zIndexPopup: 101, // dropdown z-index
|
|
|
|
// class sets
|
|
css: {
|
|
//skin: "plain", // if not set, will inherit options.skin
|
|
input: "",
|
|
inputDisabled: "disabled",
|
|
inputFocus: "focus",
|
|
|
|
button: "",
|
|
buttonIcon: "icon",
|
|
buttonDisabled: "disabled",
|
|
buttonHover: "hover",
|
|
buttonMouseDown: "mouseDown",
|
|
|
|
li: "",
|
|
liActive: "active",
|
|
|
|
hidden: "invisible",
|
|
|
|
wrapper: "ufd",
|
|
listWrapper: "list-wrapper",
|
|
listWrapperUp: "list-wrapper-up",
|
|
listScroll: "list-scroll"
|
|
},
|
|
|
|
//overlaid CSS set
|
|
uiCss: {
|
|
skin: "uiCss",
|
|
input: "ui-widget-content",
|
|
inputDisabled: "disabled",
|
|
|
|
button: "ui-button",
|
|
buttonIcon: "ui-icon ui-icon-triangle-1-s",
|
|
buttonDisabled: "disabled",
|
|
buttonHover: "ui-state-focus",
|
|
buttonMouseDown: "ui-state-active",
|
|
|
|
li: "ui-menu-item",
|
|
liActive: "ui-state-hover",
|
|
|
|
hidden: "invisible",
|
|
|
|
wrapper: "ufd ui-widget ui-widget-content",
|
|
listWrapper: "list-wrapper ui-widget ui-widget",
|
|
listWrapperUp: "list-wrapper-up",
|
|
listScroll: "list-scroll ui-widget-content"
|
|
}
|
|
}
|
|
});
|
|
|
|
$.ui.ufd.prototype.options = $.ui.ufd.defaults; // 1.8 default options location
|
|
|
|
})(jQuery);
|
|
/* END */ |