[ Disclaimer, Create new user --- Wiki markup help, Install P99 ]
Difference between revisions of "MediaWiki:HuntingGuide.js"
From Project 1999 Wiki
		
		
		
| Line 1: | Line 1: | ||
| // Basic DOM manipulation onReady (since the wiki won't let us add <input> tags in the wiki text | // Basic DOM manipulation onReady (since the wiki won't let us add <input> tags in the wiki text | ||
| − | var buildEraCheckbox = function(era) { | + | var buildEraCheckbox = function (era) { | 
| − |    return '<label><input checked class="eraFilter" type="checkbox" value="' + era.substr(0,3).toLowerCase() + '"/>' + era + '</label>'; | + |    return ( | 
| − | } | + |     '<label><input checked class="eraFilter" type="checkbox" value="' + | 
| + |     era.substr(0, 3).toLowerCase() + | ||
| + |     '"/>' + | ||
| + |     era + | ||
| + |     '</label>' | ||
| + |   ); | ||
| + | }; | ||
| var eras = ['Classic', 'Kunark', 'Velious']; | var eras = ['Classic', 'Kunark', 'Velious']; | ||
| $('#eraFilterPlaceholder').replaceWith(eras.map(buildEraCheckbox).join('')); | $('#eraFilterPlaceholder').replaceWith(eras.map(buildEraCheckbox).join('')); | ||
| − | $('#levelFilterPlaceholder').replaceWith('<input id=" | + | $('#levelFilterPlaceholder').replaceWith('<input id="level-filter" style="width:30px"/>'); | 
| − | $('#zoneFilterPlaceholder').replaceWith('<input id=" | + | $('#zoneFilterPlaceholder').replaceWith('<input id="zone-filter" style="width:50em"/>'); | 
| // Create filter results table | // Create filter results table | ||
| Line 14: | Line 20: | ||
| var $headerRow = $('.hunting-guide-row:first').clone(); | var $headerRow = $('.hunting-guide-row:first').clone(); | ||
| − | var clearFilterResults = function() { | + | var clearFilterResults = function () { | 
| − |    return $filterResults.empty() | + |    return $filterResults.empty().append($headerRow); | 
| − | + | ||
| }; | }; | ||
| clearFilterResults(); | clearFilterResults(); | ||
| $('h4:contains("Hunting Spots 1-4")').before($filterResults); | $('h4:contains("Hunting Spots 1-4")').before($filterResults); | ||
| + | |||
| + | // Add radio buttons (since MediaWiki won't allow them as content) | ||
| + | |||
| + | $('.filter-link').each(function (i, el) { | ||
| + |   var $el = $(el); | ||
| + |   var eqClass = $el.text(); | ||
| + |   var htmlClass = $el.attr('class'); | ||
| + |   var name = htmlClass.split('-', 1)[0]; | ||
| + |   var inputHtml = '<input type="radio" name="' + name + '" ' + (i ? '' : ' checked') + '/>'; | ||
| + |   $el.replaceWith('<label class="' + htmlClass + '">' + inputHtml + eqClass + '</label>'); | ||
| + | }); | ||
| // Get jQuery object of every row in the hunting guide (because all of the filter functions need it) | // Get jQuery object of every row in the hunting guide (because all of the filter functions need it) | ||
| Line 49: | Line 65: | ||
|    spots.push({ |    spots.push({ | ||
|      soloLevel: $tr.children('td:eq(0)').text(), |      soloLevel: $tr.children('td:eq(0)').text(), | ||
| − |      groupLevel, | + |      groupLevel: groupLevel, | 
|      zone: $tr.children('td:eq(2)').text(), |      zone: $tr.children('td:eq(2)').text(), | ||
|      area: $tr.children('td:eq(3)').text(), |      area: $tr.children('td:eq(3)').text(), | ||
| Line 57: | Line 73: | ||
|      image: $tr.children('td:eq(7)').text(), |      image: $tr.children('td:eq(7)').text(), | ||
|      notes: $tr.children('td:eq(8)').text(), |      notes: $tr.children('td:eq(8)').text(), | ||
| − |      $tr:$tr | + |     for: $tr.find('.for').text(), | 
| + |      $tr: $tr, | ||
|    }); |    }); | ||
| }); | }); | ||
| Line 69: | Line 86: | ||
| $('#numSpots').text(cleanedSpots.length); | $('#numSpots').text(cleanedSpots.length); | ||
| − | var  | + | var toClassAbbreviation = function (eqClass) { | 
| − | + |    return { | |
| − | + |     bard: 'BRD', | |
| − | + |     cleric: 'CLR', | |
| − | + |      druid: 'DRU', | |
| − | + |     enchanter: 'ENC', | |
| − | + |      magician: 'MAG', | |
| − | + |      monk: 'MNK', | |
| − | + |      necromancer: 'NEC', | |
| − | + |      paladin: 'PAL', | |
| − | + |      ranger: 'RNG', | |
| − | + |      'shadow knight': 'SHD', | |
| − | + |     shaman: 'SHM', | |
| − | + |     wizard: 'WIZ', | |
| − | + |    }[eqClasss.toLowerCase()]; | |
| − | + | ||
| − | + | ||
| − |    } | + | |
| − | + | ||
| }; | }; | ||
| − | var  | + | var getFilteredSpots = function ( | 
| − | + |   selectedClass, | |
| − | + |   selectedMonster, | |
| − | + |   selectedLevel, | |
| − | + |   selectedZone, | |
| − | + |   selectedEras | |
| − | + | ) { | |
| − | + |    return cleanedSpots.filter(function (spot) { | |
| + |     var classMatches = !selectedClass || spot.for.includes(toClassAbbreviation(selectedClass)); | ||
| + |     var monsterMatches = | ||
| + |       !selectedMonster || | ||
| + |       spot.monsters.includes(selectedMonster) || | ||
| + |       spot.notes.includes(selectedMonster); | ||
| − | + |     var levelRangeText = spot.$tr.children('td:first').text(); | |
| − | + |     var minLevel = parseFloat(levelRangeText.split('-')[0]); | |
| − | + |     var maxLevel = parseFloat(levelRangeText.split('-')[1] || splitLevelRanges[0]); | |
| − | + |     var levelMatches = minLevel <= level && maxLevel >= level; | |
| − | + | ||
| − | + | ||
| − | + | ||
| − | var  | + |     var lowerCaseZone = spot.zone.toLowerCase(); | 
| − | + |     var zonePattern = new RegExp(zone.toLowerCase()); | |
| − | + |     var zoneMatches = lowerCaseZone.match(zonePattern); | |
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
| − | var  | + |     var lowerCaseEra = spot.era.toLowerCase().trim(); | 
| − | + |     var eraMatches = eras.includes(lowerCaseEra); | |
| − | + | ||
| − | + | ||
| − | + | ||
| − | + |     return classMatches && monsterMatches && levelMatches && zoneMatches && eraMatches; | |
| − | + |    }); | |
| − | + | ||
| }; | }; | ||
| − | |||
| /** | /** | ||
| − |   *  | + |   * Replaces the contents of the search results UI with the rows of the provided hunting spots | 
| − | + | ||
|   */ |   */ | ||
| − | var  | + | var setResults = function (spots) { | 
| − |    var  | + |   // Clear the results (but add back the header row), then append (clones of) the result rows | 
| − | + |    var $resultsTbody = clearFilterResults(); | |
| − | + |    spots.each(function (spot) { | |
| − | + |     $resultsTbody.append(spot.$tr.clone()); | |
| + |    }); | ||
| + |   if (spots.length) return; // Show "no matches" UI only if there are no matches | ||
| + |   $resultsTbody.append( | ||
| + |     '<tr><th colspan="50" style="color:red; font-weight: bold; font-size: 2em; padding: 1em;">' + | ||
| + |       'No results for those filters</th></tr>' | ||
| + |   ); | ||
| + | }; | ||
| /** | /** | ||
| − |   *  | + |   * Re-filter the page when one of the filtering UI elements changes | 
|   */ |   */ | ||
| − | var  | + | var handleAnyFilteringElementChange = function (e) { | 
| − |    var  | + |    var noFilterWasClicked = $(e.target).is('.no-filter'); | 
| − | + |   if (noFilterWasClicked) { | |
| + |     // Clear the other radio buttons (the browser won't do it since they have different names) | ||
| + |     $('.filter-link :radio').attr('checked', false); | ||
| + |     // Clear the text inputs | ||
| + |     $('#level-filter').val(''); | ||
| + |     $('#zone-filter').val(''); | ||
| + |     // Re-check all three era checkboxes | ||
| + |     $('.era-filter').attr('checked', true); | ||
| + |    } else { | ||
| + |     $('.no-filter').attr('checked', false); | ||
| + |   } | ||
| + | |||
| + |   var selectedClass = $('.class-filter :checked').parent().text(); | ||
| + |   var selectedMonster = $('.monster-filter :checked').parent().text(); | ||
| + |   var selectedLevel = $('#level-filter').val(); | ||
| + |   var selectedZone = $('#zone-filter').val(); | ||
| + |   var selectedEras = $('.eraFilter:checked').map(function (i, el) { | ||
| + |     return $(el).val(); | ||
| + |   }); | ||
| + | |||
| + |   // Are we actually filtering out anything? | ||
| + |   var willFilter = | ||
| + |     selectedClass || selectedMonster || selectedLevel || selectedZone || selectedEras.length < 3; | ||
| + | |||
| + |   // If so, hide the normal UI and show results (otherwise show normal UI) | ||
| + |   $('h4, .wikitable').toggle(!willFilter); | ||
| + |   $filterResults.toggle(willFilter); | ||
| + | |||
| + |   var filteredSpots = getFilteredSpots( | ||
| + |     selectedClass, | ||
| + |     selectedMonster, | ||
| + |     selectedLevel, | ||
| + |     selectedZone, | ||
| + |     selectedEras | ||
| + |   ); | ||
| + |   setResults(filteredSpots); | ||
| }; | }; | ||
| + | // /** | ||
| + | //  * When there is no filter specified, restore the page to normal; when there | ||
| + | //  * is one, hide headers and the normal tables, so that we can instead show | ||
| + | //  * a filtered results table.  The results in that table are generated using the | ||
| + | //  * provided filter function/data | ||
| + | //  */ | ||
| + | // var filterSpots = function ( | ||
| + | //   filterLabel, | ||
| + | //   filter, // eg. filterByClass | ||
| + | //   filterData // eg. the class | ||
| + | // ) { | ||
| + | //   var showUnfiltered = !filter; | ||
| − | / | + | //   $('h4, .wikitable').toggle(showUnfiltered); | 
| − | + | //   $filterResults.toggle(!showUnfiltered); | |
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
| − | / | + | //   if (showUnfiltered) { | 
| − | + | //     $('#levelFilter').val(''); | |
| − | + | //   } else { | |
| − | var  | + | //     var filteredSpots = cleanedSpots.filter(function (spot) { | 
| − | + | //       return filter(spot, filterData); | |
| − | + | //     }); | |
| − | } | + | //     var rows = filteredSpots.map(function (spot) { | 
| + | //       return spot.$tr; | ||
| + | //     }); | ||
| + | //     var $resultsTbody = clearFilterResults(); | ||
| + | //     $.each(rows, function (i, row) { | ||
| + | //       var $rowClone = $(row).clone(); | ||
| + | //       $resultsTbody.append($rowClone); | ||
| + | //     }); | ||
| + | //     if (!filteredSpots.length) | ||
| + | //       $resultsTbody.append( | ||
| + | //         '<tr><th colspan="50" style="color:red; font-weight: bold; font-size: 2em; padding: 1em;">No results for those filters</th></tr>' | ||
| + | //       ); | ||
| + | //   } | ||
| + | //   selectFilter(filterLabel); | ||
| + | // }; | ||
| − | / | + | // var filterByClass = function (spot, eqClass) { | 
| − | + | //   var lowerCaseNotes = spot.notes.toLowerCase(); | |
| − | + | //   var goodForPattern = new RegExp('good for [^\\.;]*?' + eqClass.toLowerCase()); | |
| − | var  | + | //   var greatForPattern = new RegExp('great for [^\\.;]*?' + eqClass.toLowerCase()); | 
| − |    var  | + | //   var isGood = lowerCaseNotes.match(goodForPattern); | 
| − | + | //   var isGreat = lowerCaseNotes.match(greatForPattern); | |
| − | + | //   return isGood || isGreat; | |
| − | + | // }; | |
| − | } | + | |
| + | // var filterByLevel = function (spot, level) { | ||
| + | //   var levelRangeText = spot.$tr.children('td:first').text(); | ||
| + | //   var splitLevelRanges = levelRangeText.split('-'); | ||
| + | //   var minLevel = parseInt(splitLevelRanges[0], 10); | ||
| + | //   var maxLevel = parseInt(splitLevelRanges[1] || splitLevelRanges[0], 10); | ||
| + | //   return minLevel <= level && maxLevel >= level; | ||
| + | // }; | ||
| − | / | + | // var filterByMonster = function (spot, monsterType) { | 
| − | + | //   var monsterPattern = new RegExp(monsterType); | |
| − | + | //   var lowerCaseMonsters = spot.monsters.toLowerCase(); | |
| − | + | //   var lowerCaseNotes = spot.notes.toLowerCase(); | |
| − | var  | + | //   return lowerCaseMonsters.match(monsterPattern) || lowerCaseNotes.match(monsterPattern); | 
| − | + | // }; | |
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
| − | } | + | |
| − | /** | + | // var filterByZone = function (spot, zone) { | 
| − |   * Visually marks the currently-used filter with an underline | + | //   var lowerCaseZone = spot.zone.toLowerCase(); | 
| − |   */ | + | //   var zonePattern = new RegExp(zone.toLowerCase()); | 
| − | var selectFilter = function (target) { | + | //   return lowerCaseZone.match(zonePattern); | 
| − |    $('.filterLink, .monsterFilterLink, #eraFilterLabel, #levelFilterLabel, #zoneFilterLabel').css({textDecoration: ''}); | + | // }; | 
| − |    $(target).css({textDecoration: 'underline'}); | + | |
| − | } | + | // var filterByEra = function (spot, eras) { | 
| + | //   var lowerCaseEra = spot.era.toLowerCase().trim(); | ||
| + | //   return eras.includes(lowerCaseEra); | ||
| + | // }; | ||
| + | |||
| + | // /** | ||
| + | //  * Filters out (hides) rows which aren't specifically "good for" the clicked class; also handles | ||
| + | //  * the "no filter" case (TODO: Make a separate handler/event binding for it) | ||
| + | //  */ | ||
| + | // var classFilterHandler = function (e) { | ||
| + | //   var eqClass = $(e.target).text(); | ||
| + | //   if (eqClass.trim().toLowerCase() === 'no filter') filterSpots(e.target, null); | ||
| + | //   else filterSpots(e.target, filterByClass, eqClass); | ||
| + | // }; | ||
| + | |||
| + | // /** | ||
| + | //  * Filter out (hides) rows which aren't for the level the user entered | ||
| + | //  */ | ||
| + | // var levelFilterHandler = function () { | ||
| + | //   var level = parseInt($('#level-filter').val(), 10); | ||
| + | //   filterSpots('#levelFilterLabel', level && filterByLevel, level); | ||
| + | // }; | ||
| + | |||
| + | // /** | ||
| + | //  * Filters out (hides) rows which don't involve the specified monster type | ||
| + | //  */ | ||
| + | // var monsterFilterHandler = function (e) { | ||
| + | //   var monsterType = $(e.target).text().toLowerCase(); | ||
| + | //   filterSpots(e.target, filterByMonster, monsterType); | ||
| + | // }; | ||
| + | |||
| + | // /** | ||
| + | //  * Filters out (hides) rows which don't involve the specified zone | ||
| + | //  */ | ||
| + | // var zoneFilterHandler = function (e) { | ||
| + | //   var zone = $('#zone-filter').val().toLowerCase(); | ||
| + | //   filterSpots('#zoneFilterLabel', filterByZone, zone); | ||
| + | // }; | ||
| + | |||
| + | // /** | ||
| + | //  * Filters out (hides) rows which don't involve the specified era | ||
| + | //  */ | ||
| + | // var eraFilterHandler = function (e) { | ||
| + | //   var eras = $('.era-filter:checked') | ||
| + | //     .toArray() | ||
| + | //     .map(function (eraCheckbox) { | ||
| + | //       return $(eraCheckbox).val(); | ||
| + | //     }); | ||
| + | //   filterSpots('#eraFilterLabel', filterByEra, eras); | ||
| + | // }; | ||
| + | |||
| + | // /** | ||
| + | //  * Hides the table headers and level range headings for level ranges that have been completely | ||
| + | //  * filtered out | ||
| + | //  */ | ||
| + | // var hideHeaders = function () { | ||
| + | //   // If anything got hidden by previous filters, show it (because this filter might not hide it) | ||
| + | //   $('.wikitable, h4').show(); | ||
| + | //   // Hide the tables | ||
| + | //   $('.wikitable').each(function (i, table) { | ||
| + | //     var $table = $(table); | ||
| + | //     if (!$table.find('tbody').children('tr:visible').length) $table.hide(); | ||
| + | //   }); | ||
| + | //   // Hide the headings | ||
| + | //   $('h4').hide(); | ||
| + | //   // Hide level range headers | ||
| + | //   $('h4:first').next('.wikitable').show().find('tr:first').show(); | ||
| + | // }; | ||
| + | |||
| + | // /** | ||
| + | //  * Visually marks the currently-used filter with an underline | ||
| + | //  */ | ||
| + | // var selectFilter = function (target) { | ||
| + | //   $('.filterLink, .monsterFilterLink, #eraFilterLabel, #levelFilterLabel, #zoneFilterLabel').css({ | ||
| + | //     textDecoration: '', | ||
| + | //   }); | ||
| + | //   $(target).css({ textDecoration: 'underline' }); | ||
| + | // }; | ||
| // Bind event handlers | // Bind event handlers | ||
| − | $('. | + | // $('.class-filter-link').click(classFilterHandler); | 
| − | $('. | + | // $('.monster-filter-link').click(monsterFilterHandler); | 
| − | $('# | + | // $('#level-filter').change(levelFilterHandler); | 
| − | $('# | + | // $('#zone-filter').change(zoneFilterHandler); | 
| − | $('. | + | // $('.era-filter').change(eraFilterHandler); | 
| + | $('.class-filter-link, .monster-filter-link').click(handleAnyFilteringElementChange); | ||
| + | $('#level-filter, #zone-filter').keyDown(handleAnyFilteringElementChange); | ||
| + | $('.era-filter').change(handleAnyFilteringElementChange); | ||
Revision as of 19:17, 4 June 2020
// Basic DOM manipulation onReady (since the wiki won't let us add <input> tags in the wiki text
var buildEraCheckbox = function (era) {
  return (
    '<label><input checked class="eraFilter" type="checkbox" value="' +
    era.substr(0, 3).toLowerCase() +
    '"/>' +
    era +
    '</label>'
  );
};
var eras = ['Classic', 'Kunark', 'Velious'];
$('#eraFilterPlaceholder').replaceWith(eras.map(buildEraCheckbox).join(''));
$('#levelFilterPlaceholder').replaceWith('<input id="level-filter" style="width:30px"/>');
$('#zoneFilterPlaceholder').replaceWith('<input id="zone-filter" style="width:50em"/>');
// Create filter results table
var $filterResults = $('table.eoTable3.wikitable.sortable').eq(0).clone().hide();
var $headerRow = $('.hunting-guide-row:first').clone();
var clearFilterResults = function () {
  return $filterResults.empty().append($headerRow);
};
clearFilterResults();
$('h4:contains("Hunting Spots 1-4")').before($filterResults);
// Add radio buttons (since MediaWiki won't allow them as content)
$('.filter-link').each(function (i, el) {
  var $el = $(el);
  var eqClass = $el.text();
  var htmlClass = $el.attr('class');
  var name = htmlClass.split('-', 1)[0];
  var inputHtml = '<input type="radio" name="' + name + '" ' + (i ? '' : ' checked') + '/>';
  $el.replaceWith('<label class="' + htmlClass + '">' + inputHtml + eqClass + '</label>');
});
// Get jQuery object of every row in the hunting guide (because all of the filter functions need it)
var $rows = $('.wikitable tbody tr');
/**
 * Build a in-memory data object with the data parsed from each row (which will tell us which
 * levels/classes should be shown for any filter).
 * @example a "spot" created by this function for critters in Butcherblock Mountains: {
 *   soloLevel: '04-06',
 *   groupLevel: '03-05',
 *   zone: 'Butcherblock Mountains',
 *   area: 'Greater Faydark zoneline',
 *   monsters: 'Assorted Critters	',
 *   xpMod: '100%',
 *   notes: '',
 *   $tr: *a jQuery object pointing to the <tr> that all this data originally came from*
 * }
 */
var spots = [];
$rows.each(function (i, el) {
  var groupLevel = $(el).children('td:eq(1)').text();
  groupLevel = groupLevel.trim() === '-' ? null : groupLevel;
  var $tr = $(el);
  if ($tr.children('th').length) return; // Header row
  spots.push({
    soloLevel: $tr.children('td:eq(0)').text(),
    groupLevel: groupLevel,
    zone: $tr.children('td:eq(2)').text(),
    area: $tr.children('td:eq(3)').text(),
    monsters: $tr.children('td:eq(4)').text(),
    xpMod: $tr.children('td:eq(5)').text(),
    era: $tr.children('td:eq(6)').text(),
    image: $tr.children('td:eq(7)').text(),
    notes: $tr.children('td:eq(8)').text(),
    for: $tr.find('.for').text(),
    $tr: $tr,
  });
});
// Clear garbage entries (wish I had ES6 filter ..)
var cleanedSpots = [];
$.each(spots, function (i, spot) {
  if (spot.soloLevel && spot.zone) cleanedSpots.push(spot);
});
$('#numSpots').text(cleanedSpots.length);
var toClassAbbreviation = function (eqClass) {
  return {
    bard: 'BRD',
    cleric: 'CLR',
    druid: 'DRU',
    enchanter: 'ENC',
    magician: 'MAG',
    monk: 'MNK',
    necromancer: 'NEC',
    paladin: 'PAL',
    ranger: 'RNG',
    'shadow knight': 'SHD',
    shaman: 'SHM',
    wizard: 'WIZ',
  }[eqClasss.toLowerCase()];
};
var getFilteredSpots = function (
  selectedClass,
  selectedMonster,
  selectedLevel,
  selectedZone,
  selectedEras
) {
  return cleanedSpots.filter(function (spot) {
    var classMatches = !selectedClass || spot.for.includes(toClassAbbreviation(selectedClass));
    var monsterMatches =
      !selectedMonster ||
      spot.monsters.includes(selectedMonster) ||
      spot.notes.includes(selectedMonster);
    var levelRangeText = spot.$tr.children('td:first').text();
    var minLevel = parseFloat(levelRangeText.split('-')[0]);
    var maxLevel = parseFloat(levelRangeText.split('-')[1] || splitLevelRanges[0]);
    var levelMatches = minLevel <= level && maxLevel >= level;
    var lowerCaseZone = spot.zone.toLowerCase();
    var zonePattern = new RegExp(zone.toLowerCase());
    var zoneMatches = lowerCaseZone.match(zonePattern);
    var lowerCaseEra = spot.era.toLowerCase().trim();
    var eraMatches = eras.includes(lowerCaseEra);
    return classMatches && monsterMatches && levelMatches && zoneMatches && eraMatches;
  });
};
/**
 * Replaces the contents of the search results UI with the rows of the provided hunting spots
 */
var setResults = function (spots) {
  // Clear the results (but add back the header row), then append (clones of) the result rows
  var $resultsTbody = clearFilterResults();
  spots.each(function (spot) {
    $resultsTbody.append(spot.$tr.clone());
  });
  if (spots.length) return; // Show "no matches" UI only if there are no matches
  $resultsTbody.append(
    '<tr><th colspan="50" style="color:red; font-weight: bold; font-size: 2em; padding: 1em;">' +
      'No results for those filters</th></tr>'
  );
};
/**
 * Re-filter the page when one of the filtering UI elements changes
 */
var handleAnyFilteringElementChange = function (e) {
  var noFilterWasClicked = $(e.target).is('.no-filter');
  if (noFilterWasClicked) {
    // Clear the other radio buttons (the browser won't do it since they have different names)
    $('.filter-link :radio').attr('checked', false);
    // Clear the text inputs
    $('#level-filter').val('');
    $('#zone-filter').val('');
    // Re-check all three era checkboxes
    $('.era-filter').attr('checked', true);
  } else {
    $('.no-filter').attr('checked', false);
  }
  var selectedClass = $('.class-filter :checked').parent().text();
  var selectedMonster = $('.monster-filter :checked').parent().text();
  var selectedLevel = $('#level-filter').val();
  var selectedZone = $('#zone-filter').val();
  var selectedEras = $('.eraFilter:checked').map(function (i, el) {
    return $(el).val();
  });
  // Are we actually filtering out anything?
  var willFilter =
    selectedClass || selectedMonster || selectedLevel || selectedZone || selectedEras.length < 3;
  // If so, hide the normal UI and show results (otherwise show normal UI)
  $('h4, .wikitable').toggle(!willFilter);
  $filterResults.toggle(willFilter);
  var filteredSpots = getFilteredSpots(
    selectedClass,
    selectedMonster,
    selectedLevel,
    selectedZone,
    selectedEras
  );
  setResults(filteredSpots);
};
// /**
//  * When there is no filter specified, restore the page to normal; when there
//  * is one, hide headers and the normal tables, so that we can instead show
//  * a filtered results table.  The results in that table are generated using the
//  * provided filter function/data
//  */
// var filterSpots = function (
//   filterLabel,
//   filter, // eg. filterByClass
//   filterData // eg. the class
// ) {
//   var showUnfiltered = !filter;
//   $('h4, .wikitable').toggle(showUnfiltered);
//   $filterResults.toggle(!showUnfiltered);
//   if (showUnfiltered) {
//     $('#levelFilter').val('');
//   } else {
//     var filteredSpots = cleanedSpots.filter(function (spot) {
//       return filter(spot, filterData);
//     });
//     var rows = filteredSpots.map(function (spot) {
//       return spot.$tr;
//     });
//     var $resultsTbody = clearFilterResults();
//     $.each(rows, function (i, row) {
//       var $rowClone = $(row).clone();
//       $resultsTbody.append($rowClone);
//     });
//     if (!filteredSpots.length)
//       $resultsTbody.append(
//         '<tr><th colspan="50" style="color:red; font-weight: bold; font-size: 2em; padding: 1em;">No results for those filters</th></tr>'
//       );
//   }
//   selectFilter(filterLabel);
// };
// var filterByClass = function (spot, eqClass) {
//   var lowerCaseNotes = spot.notes.toLowerCase();
//   var goodForPattern = new RegExp('good for [^\\.;]*?' + eqClass.toLowerCase());
//   var greatForPattern = new RegExp('great for [^\\.;]*?' + eqClass.toLowerCase());
//   var isGood = lowerCaseNotes.match(goodForPattern);
//   var isGreat = lowerCaseNotes.match(greatForPattern);
//   return isGood || isGreat;
// };
// var filterByLevel = function (spot, level) {
//   var levelRangeText = spot.$tr.children('td:first').text();
//   var splitLevelRanges = levelRangeText.split('-');
//   var minLevel = parseInt(splitLevelRanges[0], 10);
//   var maxLevel = parseInt(splitLevelRanges[1] || splitLevelRanges[0], 10);
//   return minLevel <= level && maxLevel >= level;
// };
// var filterByMonster = function (spot, monsterType) {
//   var monsterPattern = new RegExp(monsterType);
//   var lowerCaseMonsters = spot.monsters.toLowerCase();
//   var lowerCaseNotes = spot.notes.toLowerCase();
//   return lowerCaseMonsters.match(monsterPattern) || lowerCaseNotes.match(monsterPattern);
// };
// var filterByZone = function (spot, zone) {
//   var lowerCaseZone = spot.zone.toLowerCase();
//   var zonePattern = new RegExp(zone.toLowerCase());
//   return lowerCaseZone.match(zonePattern);
// };
// var filterByEra = function (spot, eras) {
//   var lowerCaseEra = spot.era.toLowerCase().trim();
//   return eras.includes(lowerCaseEra);
// };
// /**
//  * Filters out (hides) rows which aren't specifically "good for" the clicked class; also handles
//  * the "no filter" case (TODO: Make a separate handler/event binding for it)
//  */
// var classFilterHandler = function (e) {
//   var eqClass = $(e.target).text();
//   if (eqClass.trim().toLowerCase() === 'no filter') filterSpots(e.target, null);
//   else filterSpots(e.target, filterByClass, eqClass);
// };
// /**
//  * Filter out (hides) rows which aren't for the level the user entered
//  */
// var levelFilterHandler = function () {
//   var level = parseInt($('#level-filter').val(), 10);
//   filterSpots('#levelFilterLabel', level && filterByLevel, level);
// };
// /**
//  * Filters out (hides) rows which don't involve the specified monster type
//  */
// var monsterFilterHandler = function (e) {
//   var monsterType = $(e.target).text().toLowerCase();
//   filterSpots(e.target, filterByMonster, monsterType);
// };
// /**
//  * Filters out (hides) rows which don't involve the specified zone
//  */
// var zoneFilterHandler = function (e) {
//   var zone = $('#zone-filter').val().toLowerCase();
//   filterSpots('#zoneFilterLabel', filterByZone, zone);
// };
// /**
//  * Filters out (hides) rows which don't involve the specified era
//  */
// var eraFilterHandler = function (e) {
//   var eras = $('.era-filter:checked')
//     .toArray()
//     .map(function (eraCheckbox) {
//       return $(eraCheckbox).val();
//     });
//   filterSpots('#eraFilterLabel', filterByEra, eras);
// };
// /**
//  * Hides the table headers and level range headings for level ranges that have been completely
//  * filtered out
//  */
// var hideHeaders = function () {
//   // If anything got hidden by previous filters, show it (because this filter might not hide it)
//   $('.wikitable, h4').show();
//   // Hide the tables
//   $('.wikitable').each(function (i, table) {
//     var $table = $(table);
//     if (!$table.find('tbody').children('tr:visible').length) $table.hide();
//   });
//   // Hide the headings
//   $('h4').hide();
//   // Hide level range headers
//   $('h4:first').next('.wikitable').show().find('tr:first').show();
// };
// /**
//  * Visually marks the currently-used filter with an underline
//  */
// var selectFilter = function (target) {
//   $('.filterLink, .monsterFilterLink, #eraFilterLabel, #levelFilterLabel, #zoneFilterLabel').css({
//     textDecoration: '',
//   });
//   $(target).css({ textDecoration: 'underline' });
// };
// Bind event handlers
// $('.class-filter-link').click(classFilterHandler);
// $('.monster-filter-link').click(monsterFilterHandler);
// $('#level-filter').change(levelFilterHandler);
// $('#zone-filter').change(zoneFilterHandler);
// $('.era-filter').change(eraFilterHandler);
$('.class-filter-link, .monster-filter-link').click(handleAnyFilteringElementChange);
$('#level-filter, #zone-filter').keyDown(handleAnyFilteringElementChange);
$('.era-filter').change(handleAnyFilteringElementChange);
