a21b8d5bb7d8ffa63069c412ad71fbaeeb9f4e55 angie Wed Jun 25 11:00:52 2014 -0700 Fixing bug found by Jonathan: with clean cart, when showing contents offilter section for Regulatory Elements, the multiselects didn't appear. Turns out that the 'force' argument of ddcl.reinit actually makes it do nothing! To avoid disrupting anything else, for now I'll just forgo force. refs #11461 diff --git src/hg/js/ddcl.js src/hg/js/ddcl.js index 5d93020..2e57ebb 100644 --- src/hg/js/ddcl.js +++ src/hg/js/ddcl.js @@ -1,554 +1,555 @@ // The "ddcl" object that contains all the DDCL extension/support code. // DDCL: drop-down checkbox-list wrapper code to ui.dropdownchecklist.js jQuery widget. // The reason for extension/support code beyond the jquery widget is that // We have changed some widget functionality: // - Closed control show all currently selected options in multiple lines with CSS // - Open control can gray out invalid choices // NOTE: // Currently tied to ui.dropdownchecklist.js v1.3, with 2 "UCSC" changes: // - Chrome multi-select required changing ddcl code as per issue 176. // - IE needed special code to block window.resize event in DDCL. // *** v1.4 has been released which works with jquery 1.6.1 *** // Some useful names defined: // ddcl: This object just contains these supporting functions in one package. // multiSelect: original <SELECT multiple> that ddcl wraps around // (AKA filterBy: all mutiSelects are of class .filterBy [may also be .filterComp, .filterTable]) // selectOptions: original multiSelect.options (get updated, sent to cart, etc.) // control: The whole gaggle of elements that get created when one multiSelect becomes a ddcl // controlLabel: Just the text inside the closed control // controlSelector: the closed box waiting to be clicked // dropWrapper: The div that contains all the checkboxes and is only seen when the DDCL is open // allCheckboxes: one to one correspondence with selectOptions // Don't complain about line break before '||' etc: /* jshint -W014 */ var ddcl = { //mySelf: null, // There is no need for a "mySelf" unless this object is being instantiated. textOfObjWrappedInStyle: function (obj) { // returns the obj text and if there is obj style, the text gets span wrapped with it var text = ''; var style = $(obj).attr('style'); if (style && style.length > 0) text = "<span style='"+style+"'>"; text += $(obj).text(); if (style && style.length > 0) text += "</span>"; return text; }, textOfCurrentSelections: function (options) { // Generates a multi-line string of currently selected options var chosen = $(options).filter(':selected'); // Works with FF and Chrome but not IE! if (chosen.length === 0 && $.browser.msie) chosen = $(options).find(':selected'); // Works with IE but not FF and Chrome! var chosenCount = $(chosen).length; var msg = ''; if (chosenCount === 0) { msg = 'Please select...'; } else if (chosenCount === 1) { msg = ddcl.textOfObjWrappedInStyle(chosen[0]); } else if (chosenCount === options.length) { msg = ddcl.textOfObjWrappedInStyle(options[0]); } else { for (var ix=0;ix<chosenCount;ix++) { if (ix > 0) msg += "<BR>"; msg += ddcl.textOfObjWrappedInStyle(chosen[ix]); } } return msg; }, labelSet: function (control,msg,newTextColor,newTitle) { // Sets the label text (as opposed to the drop-down options) var controlLabel = $(control).find(".ui-dropdownchecklist-text"); var controlSelector = $(control).find(".ui-dropdownchecklist-selector"); var newHeight = msg.split('<BR>').length * 20; //$(control).css('height',newHeight + 'px'); $(controlSelector).css({height: newHeight + 'px', background: '#fff'}); $(controlLabel).attr('title',newTitle); $(controlLabel).css({height: newHeight + 'px'}); $(controlLabel).css('color',newTextColor ); // could be empty string, thus removing color $(controlLabel).html(msg); }, onOpen: function (event) { // Called by a DDCL onClick event (when the drop list is opened) var controlSelector = this; // Set the label var control = $(controlSelector).parent(); ddcl.labelSet(control,"Select multiple...",'#000088','Selecting...'); // Find the active 'items' and original 'options' var id = $(control).attr('id').substring('ddcl-'.length); var dropWrapper = $('#ddcl-' + id + '-ddw');//.first(); var multiSelect = $(document.getElementById(id)); var allCheckboxes = $(dropWrapper).find("input.active"); var selectOptions = multiSelect[0].options; // Special juice to handle "exclude" options based upon competing filterBoxes try { if (( $(multiSelect).hasClass('filterComp') && filterCompositeExcludeOptions(multiSelect)) || ( $(multiSelect).hasClass('filterTable') && filterTable.excludeOptions(multiSelect))) { // "exclude" items based upon the exclude tag of the true options allCheckboxes.each(function(index) { var item = $(this).parent(); if ($(selectOptions[index]).hasClass('excluded')) { $(item).addClass("ui-state-excluded"); } else //if ($(item).hasClass("ui-state-excluded")) $(item).removeClass("ui-state-excluded"); }); } } catch (err) {} // OK if filterCompositeExcludeOptions is not defined. // Show only first as selected if it is selected if (allCheckboxes[0].checked === true) { allCheckboxes.each(function(index) { if (index > 0) $(this).attr('checked',false); }); } }, onComplete: function (multiSelect) { // Called by ui.dropdownchecklist.js when selections have been made // Also called at init to fill the selector with current choices // Warning: In IE this gets called when still selecting! var id = $(multiSelect).attr('id'); // If no options are selected, may have to force all var chosen = $(multiSelect).find('option:selected'); if (chosen.length === 0) { if ($(multiSelect).hasClass('noneIsAll')) { //$(multiSelect).first('option').first().attr('selected',true); multiSelect.options[0].selected = true; // How to check the first item? var dropWrapper = $('#ddcl-' + id + '-ddw'); $(dropWrapper).find("input").first().attr("checked",true); } } else if (chosen.length === $(multiSelect).find('option').length) { // If all are chosen then select only the first! $(chosen).each(function(index) { if (index > 0) $(this).attr('selected',false); }); } var msg = ddcl.textOfCurrentSelections(multiSelect.options); var control = $('#ddcl-' + id); var newColor = ''; if ($(multiSelect).find('option:selected').length === 0) newColor = '#AA0000'; // red //else if (msg.search(/color:/i) === -1) // newColor = 'black'; ddcl.labelSet(control,msg,newColor,'Click to select...'); // Notice special handling for a custom event $(multiSelect).trigger('done',multiSelect); }, reinit: function (filterBys,force) { // ReInitialize the DDCLs (drop-down checkbox-list) // This is done when the track search with tabs gets switched to advanced tab // because the DDCLs were setup on hidden filterBys and dimensiuons are wrong. // if not force, then only reinit when the dimensions are suspect +// NOTE: If force is true, this doesn't do anything!! if (filterBys.length < 1) return; $(filterBys).each( function(i) { // Do this by 'each' to set noneIsAll individually var multiSelect = this; if (!force) { // condition on bad dimensions var id = $(multiSelect).attr('id'); var control = normed($('#ddcl-' + id)); if (!control) { // Object never initialized so do it now. //$(multiSelect).show(); // necessary to get dimensions ddcl.setup(multiSelect,'noneIsAll'); } else { // This is being called before normal init var controlSelector = $(control).find(".ui-dropdownchecklist-selector"); if ($(controlSelector).width() <= 20) { // Initialized before fully visible so do it again. $(multiSelect).dropdownchecklist("destroy"); $(multiSelect).show(); // necessary to get dimensions ddcl.setup(multiSelect,'noneIsAll'); } // else dimensions look okay } } }); }, setup: function (obj) { // Initialize the multiselect as a DDCL (drop-down checkbox-list) //mySelf = this; // There is no need for a "mySelf" unless this object is being instantiated // Defaults var myFirstIsAll = true; var myNoneIsAll = false; var myIcon = null; var myEmptyText = 'Select...'; var myClose = 'close '; var myDropHeight = filterByMaxHeight(obj); // parse optional args for (var vIx=1;vIx<arguments.length;vIx++) { switch(arguments[vIx]) { case 'noneIsAll': myNoneIsAll = true; break; case 'firstNotAll': myFirstIsAll = false; break; case 'arrows': myIcon = {}; break; case 'noClose': myClose = null; break; case 'label': vIx++; if (vIx<arguments.length) myEmptyText = arguments[vIx]; break; default: warn('ddcl.setup() unexpected argument: '+arguments[vIx]); break; } } if (myFirstIsAll === false) myNoneIsAll = false; // Make sure there is an id! var id = $(obj).attr('id'); if (!id || id.length === 0) { var name = $(obj).attr('name'); if (name && name.length > 0) id = 'dd-' + name; else { id = 'ix' + $('select').index(obj); } $(obj).attr('id',id); } else { if (normed($('#ddcl-' + id))) // Don't set up twice! return; } // These values can only be taken from the select before it becomes a DDCL var maxWidth = $(obj).width(); if (maxWidth === 0) // currently hidden so wait for a reinit(); return; var minWidth = $(obj).css('min-width'); if (minWidth && minWidth.length > 0) { // Is a string, so convert and compare minWidth = parseInt(minWidth); if (maxWidth < minWidth) maxWidth = minWidth; } maxWidth = (Math.ceil(maxWidth / 10) * 10) + 10; // Makes for more even boxes var style = $(obj).attr('style'); // The magic starts here: $(obj).dropdownchecklist({ firstItemChecksAll: true, noneIsAll: myNoneIsAll, maxDropHeight: myDropHeight, icon: myIcon, emptyText: myEmptyText, explicitClose: myClose, textFormatFunction: function () { return 'selecting...'; } , onComplete: ddcl.onComplete }); if (myNoneIsAll) $(obj).addClass('noneIsAll'); // Declare this as none selected same as all selected ddcl.onComplete(obj); // shows selected items in multiple lines // Set up the selector (control seen always and replacing select) var control = $('#ddcl-' + id); if (!control) { warn('ddcl.setup('+id+') failed to create drop-down checkbox-list'); return; } var controlSelector = $(control).find(".ui-dropdownchecklist-selector"); $(controlSelector).click(ddcl.onOpen); $(controlSelector).css({width:maxWidth+'px'}); var controlText = $(control).find(".ui-dropdownchecklist-text"); $(controlText).css({width:maxWidth+'px'}); // Set up the drop list (control seen only on fucus and with items to choose) var dropWrapper = $('#ddcl-' + id + '-ddw'); if (!dropWrapper) { warn('ddcl.setup('+id+') failed to create drop-down checkbox-list'); return; } // Individual items need styling var itemHeight = 22; // Exclude the close button var dropItems = $(dropWrapper).find(".ui-dropdownchecklist-item"); $(dropItems).hover(function () {$(this).css({backgroundColor:'#CCFFCC'});}, function () {$(this).css({backgroundColor:'white'});}); dropItems = $(dropItems).not('.ui-dropdownchecklist-close'); $(dropItems).css({background:'white', borderStyle:'none', height:itemHeight+'px'}); var itemCount = dropItems.length; if (myClose && myClose.length > 0) { // target the close button var dropClose = $(dropWrapper).find(".ui-dropdownchecklist-close"); $(dropClose).css({height:(itemHeight - 1)+'px',textAlign:'center'}); itemCount++; } // The whole droplist needs styling var dropContainerDiv = dropWrapper.find(".ui-dropdownchecklist-dropcontainer"); var maxHeight = (itemHeight*itemCount) + 1; // extra prevents unwanted vertical scrollbar var divHeight = dropContainerDiv.outerHeight(); if (divHeight > maxHeight) { $(dropContainerDiv).css({height:maxHeight+'px'}); $(dropWrapper).css({height:maxHeight+'px'}); } maxWidth += 30; // extra avoids horizontal scrollBar when vertical one is included $(dropContainerDiv).css({width:(maxWidth)+'px'}); $(dropWrapper).css({width:maxWidth+'px'}); // Finally we can get style from the original select and apply it to the whole control if (style && style.length > 0) { var styles = style.split(';'); for (var ix = 0;ix < styles.length;ix++) { var aStyleDef = styles[ix].split(':'); aStyleDef[0] = aStyleDef[0].replace(' ',''); // no spaces if (aStyleDef[0] !== 'display') // Need to see if other styles should be restricted. $(control).css(aStyleDef[0],aStyleDef[1]); if (aStyleDef[0].substring(0,4) === 'font') // Fonts should be applied too $(dropItems).css(aStyleDef[0],aStyleDef[1]); } } }, start: function () { // necessary to delay startup till all selects get ids. $('.filterBy,.filterComp').each( function(i) { if ($(this).hasClass('filterComp')) ddcl.setup(this); // Not nonIsAll else ddcl.setup(this,'noneIsAll'); }); } }; /////////////////////// //// Filter Tables //// /////////////////////// var filterTable = { // Filter Tables are HTML tables that can be filtered by drop-down-checkbox-lists (ddcl) controls // To use this feature trs must have class 'filterble' and contain // tds with classes matching category and value // eg: <TR class='filterable'><TD class='cell 8988T'>8988T</td> // and ddcl 'filterBy' controls with additional classes 'filterTable' and the category, // and contains options with the the value to be filtered. // Also, the ddcl objects onChange event must call filterTable.filter(this) // eg: <SELECT name='cell' MULTIPLE class='filterTable cell filterBy' // onchange='filterTable.filter(this);' style='font-size:.9em;'> // <OPTION SELECTED VALUE='All'>All</OPTION> // <OPTION VALUE='8988T'>8988T</OPTION> // Multiple filterTable controls can be defined, but only one table can be the target of filtering. variable: function (filter) { // returns the variable associated with a filter if ($(filter).hasClass('filterBy') === false) return undefined; if ($(filter).hasClass('filterTable') === false) return undefined; // Find the var for this filter var classes = $(filter).attr("class").split(' '); classes = aryRemove(classes,["filterBy","filterTable","noneIsAll"]); if (classes.length > 1 ) { warn('Too many classes for filterBy: ' + classes); return undefined; } return classes.pop(); }, applyOneFilter: function (filter,remainingTrs) { // Applies a single filter to a filterTables TRs var classes = $(filter).val(); if (!classes || classes.length === 0) return null; // Nothing selected so exclude all rows if (classes[0] === 'All') return remainingTrs; // nothing excluded by this filter // Get the filter variable var filterVar = filterTable.variable(filter); if (!filterVar || filterVar.length === 0) return null; var varTds = $(remainingTrs).children('td.' + filterVar); var filteredTrs = null; var ix =0; for (;ix<classes.length;ix++) { var tds = $(varTds).filter('.' + classes[ix]); if (tds.length > 0) { var trs = $(tds).parent(); if (!filteredTrs) filteredTrs = trs; else filteredTrs = jQuery.merge( filteredTrs, trs ); // takes too long in IE! } } return filteredTrs; }, trsSurviving: function (filterClass) // returns a list of trs that satisfy all filters // If defined, will exclude filter identified by filterClass { // find all filterable table rows var showTrs = $('tr.filterable'); // Default all if (showTrs.length === 0) return undefined; // Find all filters var filters = $("select.filterBy"); if (filters.length === 0) return undefined; // Exclude one if requested. if (filterClass && filterClass.length > 0) filters = $(filters).not('.' + filterClass); for (var ix=0;showTrs && ix < filters.length;ix++) { showTrs = filterTable.applyOneFilter(filters[ix],showTrs); } return showTrs; }, _filter: function () { // Called by filter onchange event. Will show/hide trs based upon all filters var showTrs = filterTable.trsSurviving(); //$('tr.filterable').hide(); // <========= This is what is taking so long! $('tr.filterable').css('display', 'none'); var counter = $('.filesCount'); if (showTrs && showTrs.length > 0) { //$(showTrs).show(); $(showTrs).css('display', ''); // Update count if (counter) $(counter).text($(showTrs).length + " / "); } else { if (counter) $(counter).text(0 + " / "); } var tbody = $( $('tr.filterable')[0] ).parent('tbody.sorting'); if (tbody) $(tbody).removeClass('sorting'); }, trigger: function () { // Called by filter onchange event. Will show/hide trs based upon all filters var tbody = $( $('tr.filterable')[0] ).parent('tbody'); if (tbody) $(tbody).addClass('sorting'); waitOnFunction(filterTable._filter); }, done: function (event) { // Called by custom 'done' event event.stopImmediatePropagation(); $(this).unbind( event ); filterTable.trigger(); }, filter: function (multiSelect) { // Called by filter onchange event. Will show/hide trs based upon all filters // IE takes tooo long, so this should be called only when leaving the filterBy box if ( $('tr.filterable').length > 300) { $(multiSelect).one('done',filterTable.done); return; } else filterTable.trigger(); }, excludeOptions: function (filter) { // bound to 'click' event inside ddcl.js. // Will mark all options in one filterBy box that are inconsistent with the current // selections in other filterBy boxes. Mark with class ".excluded" // Compare to the list of all trs var allTrs = $('tr.filterable'); // Default all if (allTrs.length === 0) return false; // IE takes tooo long, so this should be called only when leaving the filterBy box if ($.browser.msie && $(allTrs).length > 300) return false; // Find the var for this filter var filterVar = filterTable.variable(filter); if (!filterVar) return false; // Look at list of visible trs. var visibleTrs = filterTable.trsSurviving(filterVar); if (!visibleTrs) return false; if (allTrs.length === visibleTrs.length) { $(filter).children('option.excluded').removeClass('excluded'); return true; // Nothing more to do. All are already excluded } // Find the tds that belong to this var var tds = $(visibleTrs).find('td.'+filterVar); if (tds.length === 0) { $(filter).children('option').addClass('excluded'); // add .excluded" to all return true; } // Find the val classes var classes = []; // i.e. new Array(); $(tds).each(function (i) { var someClass = $(this).attr("class").split(' '); someClass = aryRemove(someClass,[filterVar]); var val = someClass.pop(); if (aryFind(classes,val) === -1) classes.push(val); }); if (classes.length === 0) { $(filter).children('option').addClass('excluded'); // add .excluded" to all return true; } // Find all options with those classes $(filter).children('option').each(function (i) { if (aryFind(classes,$(this).val()) !== -1) $(this).removeClass('excluded'); // remove .excluded from matching else $(this).addClass('excluded'); // add .excluded" to non-matching }); // If all options except "all" are included then all should nt be excluded var excluded = $(filter).children('option.excluded'); if (excluded.length === 1) { var text = $(excluded[0]).text(); if (text === 'All' || text === 'Any') $(excluded[0]).removeClass('excluded'); } return true; } }; $(document).ready(function() { setTimeout(ddcl.start,2); // necessary to delay startup till all selects get ids. });