Widget:PageFilterJS
Jump to navigation
Jump to search
<script type="application/javascript">
// Widget:PageFilterJS
(function() {
const addEventForChild = function(parent, eventName, childSelector, callback){
parent.addEventListener(eventName, function(event){
const clickedElement = event.target;
const matchingChild = clickedElement.closest(childSelector);
if (matchingChild)
callback(event, matchingChild);
});
};
const getPageFilterGroup = function(element) {
if (element.pageFilterGroupID)
return element.pageFilterGroupID;
for (let e = element; e; e = e.parentElement)
if (e.dataset && e.dataset.pageFilterGroupId)
return element.pageFilterGroupID = e.dataset.pageFilterGroupId;
return element.pageFilterGroupID = 'g0';
};
const getFilterElement = function(filterID, groupID) {
for (const e of document.querySelectorAll('.page-filter-group-' + filterID)) {
if (getPageFilterGroup(e.parentElement) == groupID)
return e;
}
// TODO: remove this fallback to element ID
var eid = document.getElementById('filter-'+ filterID);
if (eid && getPageFilterGroup(eid) == groupID) return eid;
return null;
};
const getFilter = function(name, groupID) {
const filterElement = getFilterElement(name, groupID);
const filters = filterElement ? filterElement.querySelectorAll("label.mw-ui-progressive") : "";
if (filters.length <= 0)
return false;
const result = [];
for (const filter of filters) {
if (filter.dataset.value === '*')
return true;
const values = filter.dataset.value.split(';');
for (const value of values) {
result.push(value);
};
}
return result;
};
const filterDefinitions = [
['Owned', 'short-id', ['All','*', 'Yes','true', 'No','false']],
['Rarity', 'filter-rarity', ['All','*', 'SSR','ssr', 'SR','sr', 'R','r']],
['Element', 'filter-element', ['All','*', 'Fire','fire', 'Water','water', 'Earth','earth', 'Wind','wind', 'Light','light', 'Dark','dark', 'Any','any']],
['Race', 'filter-race', ['All','*', 'Human','human', 'Erune','erune', 'Draph','draph', 'Harvin','harvin', 'Primal','primal', 'Unknown','unknown', 'Other','other']],
['Specialty', 'filter-weapon', ['All','*', 'Sabre','sabre', 'Dagger','dagger', 'Spear','spear', 'Axe','axe', 'Staff','staff', 'Gun','gun', 'Melee','melee', 'Bow','bow', 'Harp','harp', 'Katana','katana']],
['Weapon Type', 'filter-type', ['All','*', 'Sabre','sabre', 'Dagger','dagger', 'Spear','spear', 'Axe','axe', 'Staff','staff', 'Gun','gun', 'Melee','melee', 'Bow','bow', 'Harp','harp', 'Katana','katana']],
['Series', 'filter-series', ['All','*','None','none','Vintage','vintage','Epic','epic','Vyrmament','vyrmament','Olden Primal','olden primal','Ultima','ultima','Cosmos','cosmos','Superlative','superlative','Sephira','sephira','Seraphic','seraphic','Bahamut','bahamut','Primal','primal','Omega','omega','Grand','grand','Regalia','regalia','Rose','rose','Relics','relic','Class Champion','ccw','Rusted','rusted','Beast','beast','Revenant','revenant','Replicas','replica','Xeno','xeno','Hollowsky','hollowsky','Dark Opus','dark opus','Astral','astral','Draconic','draconic','Weapons of Eternal Splendor','splendor','Ancestral','ancestral','New World Foundation','newworld','Upgraders','upgrader','Eternals','eternals','Evokers','evokers','12 Generals','12generals','Valentine','valentine','Summer','summer','Halloween','halloween','Holiday','holiday','Yukata','yukata','Fantasy','fantasy','Tie-in','tie-in']]
];
const addFilterUI = function(container) {
if (!container) return;
let filterGroup = getPageFilterGroup(container);
for (const ex of document.querySelectorAll('[data-extra-filter]')) {
if (ex.dataset && ex.dataset.extraFilter && ex.dataset.extraFilter.match(/^[a-z:A-Z0-9_ \-*()]*$/) && getPageFilterGroup(ex) == filterGroup) {
let filterEntry = ex.dataset.extraFilter.split(':');
if (ex.dataset.filterType && ex.dataset.filterType == 'dynamic') {
let dynamicFilters = ['All', '*'];
let kv = {};
for (let ex2 of document.querySelectorAll('[data-'+filterEntry[1]+']')) {
let ftags = ex2.dataset[filterEntry[1]].split(/\s*,\s*/);
for(let ftag of ftags) {
if (kv[ftag]) { continue; }
let fname = ftag.replaceAll('-',' ').trim();
if (''==fname) { continue; }
fname = fname[0].toUpperCase() + fname.slice(1);
kv[ftag] = fname;
}
}
for (let tag in kv) {
dynamicFilters.push(kv[tag]);
dynamicFilters.push(tag);
}
filterEntry[2] = dynamicFilters;
} else {
filterEntry[2] = filterEntry.slice(2);
}
filterDefinitions.push(filterEntry.slice(0,3));
}
}
let skipFilters = {}, whitelistFilters = false;
if (container.dataset.skipFilters) {
for (const f of container.dataset.skipFilters.match(/[^, ]+/g)) {
skipFilters[f] = true;
}
}
if (container.dataset.onlyFilter) {
whitelistFilters = {};
for (const f of container.dataset.onlyFilter.match(/[^, ]+/g)) {
whitelistFilters[f] = true;
}
}
let hasFilters = false;
for (let i = 0, g; g = filterDefinitions[i]; i++) {
let groupName = g[0], groupData = g[1], groupList = g[2];
if (skipFilters[groupData] || (whitelistFilters && !whitelistFilters[groupData])) continue;
if (getFilterElement(groupData, filterGroup)) continue;
if (groupData == 'short-id' && !(window.CollectionTrackerState && window.CollectionTrackerState.hasSavedData())) continue;
const entries = document.querySelectorAll('[data-' + groupData + ']');
if (entries.length <= 1) continue;
const dataKey = groupData.replace(/-([a-z])/g, function(_, a) { return a.toUpperCase(); } );
let hasEntry = {}, hasDistinctValues = false, firstValue = entries[0].dataset[dataKey];
if (groupData != 'short-id') {
for (const entry of entries) {
if (getPageFilterGroup(entry) != filterGroup) continue;
let ev = entry.dataset[dataKey];
if (!ev) continue;
if (ev != firstValue) hasDistinctValues = true;
for (const fv of ev.matchAll(/[^,]+/g)) {
hasEntry[fv[0].trim()] = true;
}
}
if (!hasDistinctValues) continue;
hasEntry['*'] = true;
}
let div = document.createElement('div');
div.className = "mw-ui-button-group page-filter-group page-filter-group-" + groupData;
let label = document.createElement('label');
label.className = "mw-ui-button mw-ui-disabled label";
label.appendChild(document.createTextNode(groupName));
div.appendChild(label);
let opts = document.createElement('div');
opts.className = "mw-ui-button-group items";
div.appendChild(opts);
let hasOptions = true;
for (let i = 0; i < groupList.length; i+=2) {
if (!hasEntry[groupList[i+1]] && groupData != 'short-id') continue;
let b = document.createElement('label');
b.className = "mw-ui-button";
b.dataset.value = groupList[i+1];
b.appendChild(document.createTextNode(groupList[i]));
opts.appendChild(b);
hasOptions = hasOptions || (groupList[i+1] != '*');
}
if (!hasOptions) continue;
if (hasFilters) {
let clr = document.createElement('p');
clr.className = 'visualClear';
clr.style.height = '1px';
container.appendChild(clr);
}
container.appendChild(div);
hasFilters = true;
}
let tr = document.querySelector('tr');
if (tr) {
let ov = tr.style.visibility, oh = tr.offsetHeight;
tr.style.visibility = 'collapse';
if (tr.offsetHeight >= oh && oh > 0) {
var style = document.createElement('style');
style.appendChild(document.createTextNode('tr.hide {display: none}'));
document.head.appendChild(style);
}
tr.style.visibility = ov;
}
};
const refilter = function(updateGroup) {
const CollectionTrackerState = window.CollectionTrackerState;
console.time('check filters');
const filters = {}, filterDataKeys = {};
let elementSelectors = [];
for (const fg of filterDefinitions) {
const filterID = fg[1];
const val = getFilter(filterID, updateGroup);
if (val !== false) {
elementSelectors.push('[data-' + filterID + ']');
filters[filterID] = val;
filterDataKeys[filterID] = filterID.replace(/-([a-z])/g, function(_, a) { return a.toUpperCase(); } )
}
}
const filterOwned2 = typeof filters["short-id"] == "object" ? filters["short-id"][0] === 'true' : null;
if (elementSelectors.length <= 0)
return;
const elementSelector = elementSelectors.join(',')
console.timeEnd('check filters');
console.time('select elements');
const elements = document.querySelectorAll(elementSelector);
console.timeEnd('select elements');
console.time('update elements');
for (const element of elements) {
if (getPageFilterGroup(element) != updateGroup) continue;
let visible = true;
for (let fkey in filters) {
let fval = filters[fkey];
let ev = element.dataset[filterDataKeys[fkey]];
if (typeof fval != 'object') continue;
if (typeof ev == 'undefined') continue;
if (fkey == 'short-id') {
if (CollectionTrackerState.isObtained(ev, element.dataset.type) == filterOwned2) continue;
} else {
if (element.dataset[fkey] === '*') continue;
if (fval.indexOf(ev) >= 0) continue;
if (ev && ev.indexOf(",")) {
let found = false;
for (let ev2data of ev.split(/\s*,\s*/)) {
if (fval.indexOf(ev2data) >= 0) {
found = true;
break;
}
}
if (found) continue;
}
}
visible = false;
break;
}
element.classList.toggle('hide', !visible);
}
console.timeEnd('update elements');
console.time('update filterable dividers');
let rules = document.querySelectorAll('.filterable-divider');
for (let i = 0, j; i < rules.length; i++) {
let p = rules[i].parentNode;
for (j = 0; j < i; j++) {
if (p == rules[j].parentNode) {
break;
}
}
if (j < i) continue;
if (getPageFilterGroup(p) != updateGroup) continue;
let hasVisibleContent = false, lastVisibleDivider = false, lastHiddenDivider = false;
for (let e = p.firstElementChild; e; e = e.nextElementSibling) {
if (e.classList.contains('filterable-divider')) {
e.classList.toggle('hide', !hasVisibleContent);
lastVisibleDivider = hasVisibleContent ? e : lastVisibleDivider;
lastHiddenDivider = hasVisibleContent ? false : e;
hasVisibleContent = false;
} else if (!hasVisibleContent && !e.classList.contains('hide')) {
if (lastVisibleDivider && lastHiddenDivider) {
lastVisibleDivider.classList.toggle('hide', true);
lastHiddenDivider.classList.toggle('hide', false);
lastVisibleDivider = lastHiddenDivider;
lastHiddenDivider = false;
}
hasVisibleContent = true;
}
}
if (!hasVisibleContent && lastVisibleDivider) {
lastVisibleDivider.classList.toggle('hide', true);
}
}
console.timeEnd('update filterable dividers');
};
// Generate filter UI
// TODO: remove ID variant
document.querySelectorAll('#page-filter-container, .page-filter-container').forEach(addFilterUI);
// page setup below this point
document.querySelectorAll('.page-filter-group label.mw-ui-progressive').forEach(function(label) { label.classList.remove('mw-ui-progressive'); });
document.querySelectorAll('.page-filter-group .items > label:first-child').forEach(function(label) { label.classList.add('mw-ui-progressive'); });
// Filter from the start
for (let label of document.querySelectorAll('.page-filter-group .items > label:not(.mw-ui-disabled)')) {
let group = label.parentElement;
const filterGroup = getPageFilterGroup(group.parentElement);
setTimeout(function() {
refilter(filterGroup);
}, 0);
}
addEventForChild(document, 'click', '.page-filter-group .items > label:not(.mw-ui-disabled)', function(event, label) {
console.time('Click Label');
let group = label.parentElement;
let all = group.querySelector('label[data-value="*"]');
const filterGroup = getPageFilterGroup(group.parentElement);
if (label === all) {
if (!all.classList.contains('mw-ui-progressive')) {
group.querySelectorAll('label').forEach(function(sibling) {
sibling.classList.remove('mw-ui-progressive');
});
all.classList.add('mw-ui-progressive');
}
} else if (group.childElementCount <= 3) {
group.querySelectorAll('label').forEach(function(sibling) {
sibling.classList.remove('mw-ui-progressive');
});
label.classList.add('mw-ui-progressive');
} else {
if (all) all.classList.remove('mw-ui-progressive');
label.classList.toggle('mw-ui-progressive');
if (group.querySelectorAll('.mw-ui-progressive').length < 1 && all)
all.classList.add('mw-ui-progressive');
}
setTimeout(function() {
console.time('Click Label Update');
refilter(filterGroup);
console.timeEnd('Click Label Update');
}, 0);
console.timeEnd('Click Label');
});
})();
</script>