NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name City lists // @namespace torn.com // @version 1.7 // @author Ahab [1735214] // @license MIT // @updateURL https://openuserjs.org/meta/Ahab/City_lists.meta.js // @match https://www.torn.com/city.php // @run-at document-body // @grant GM_addStyle // @grant GM.xmlHttpRequest // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @require https://gist.githubusercontent.com/BrockA/2625891/raw/9c97aa67ff9c5d56be34a55ad6c18a314e5eb548/waitForKeyElements.js // @require https://raw.githubusercontent.com/lostandconfused/test/master/count.js // @require https://jpillora.com/xhook/dist/xhook.js // ==/UserScript== GM_addStyle(` table { text-align: center; width: 100%; table-layout: auto; border-style: hidden; } tbody:not(#idata).tab tr:nth-child(odd) { background-color: var(--default-bg-panel-active-color); } thead{ font-size: 130%; font-weight: bold; } .tab tr, .tab td, .tab th, .cell { border: 1px solid var(--default-panel-divider-outer-side-color)!important; vertical-align: middle!important; padding: 5px!important; color: var(--default-qlinks-color); } #wdata tr td:nth-child(1) { width: 100px; } #wdata tr td:nth-child(3) { width: 206px; } .tab a:hover, #listSelections span:hover, thead.tab th:hover, .symPane:hover, .cell:hover, #warProgress:hover{ color: var(--default-blue-hover-color)!important } #listSelections span, thead.tab, .symPane, .cell, #warProgress{ cursor: pointer } .tab a, #warProgress{ color:var(--default-blue-color); text-decoration: none; margin-top: 1px } .activetab{ display: inline-table } .inactive{ display: none } .selectedtab{ color: #5f9ea0 } .addTime{ padding: 3px; width: 100% } .wImg{ align-items: center; justify-content: center; display: flex; left: 15px; position: relative } #rImg{ margin-top: 1px; width: 25px; padding-left: 5px; left: 3px; position: relative } .advantage-bar { width: 100%; height: 5px; display: table; border-radius: 100px; margin-bottom: 4px; margin-top: 5px } .advantage-bar .your { display: table-cell; background: #a3d900; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2EzZDkwMCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiM2OThjMDAiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); background: -moz-linear-gradient(top,rgba(163,217,0,1) 0%,rgba(105,140,0,1) 100%); background: -webkit-linear-gradient(top,rgba(163,217,0,1) 0%,rgba(105,140,0,1) 100%); background: linear-gradient(to bottom,rgba(163,217,0,1) 0%,rgba(105,140,0,1) 100%); border-radius: 100px 0 0 100px; position: relative } .advantage-bar .your:nth-child(2) { border-radius: 0 100px 100px 0 } .advantage-bar .enemy { display: table-cell; background: #ff7a4d; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmN2E0ZCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNkOTM2MDAiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); background: -moz-linear-gradient(top,rgba(255,122,77,1) 0%,rgba(217,54,0,1) 100%); background: -webkit-linear-gradient(top,rgba(255,122,77,1) 0%,rgba(217,54,0,1) 100%); background: linear-gradient(to bottom,rgba(255,122,77,1) 0%,rgba(217,54,0,1) 100%); border-radius: 0 100px 100px 0; position: relative } .advantage-bar .enemy:nth-child(1) { border-radius: 100px 0 0 100px } .advantage-bar .your .animation, .advantage-bar .enemy .animation { height: 5px; width: 100%; position: relative; top: 0; left: 0; overflow: hidden } .advantage-bar .your .animation .arrows, .advantage-bar .enemy .animation .arrows { height: 5px; width: auto; position: relative; top: 0; left: -5px; background: url(/images/v2/faction/wars/progress_bar_arrows.png) 0 -30px repeat-x; -webkit-animation: advantage-bar-animation 30s linear .1s infinite normal; animation: advantage-bar-animation 30s linear .1s infinite normal; z-index: 8 } .advantage-bar .enemy .animation .arrows { left: -5px } .advantage-bar .your:nth-child(2) .animation .arrows { left: 5px; background-position-y: -10px; -webkit-animation-direction: reverse; animation-direction: reverse; z-index: 8 } .advantage-bar .enemy:nth-child(1) .animation .arrows { left: 5px; background-position-y: -20px; -webkit-animation-direction: normal; animation-direction: normal; z-index: 8 } .advantage-bar .your .animation:before, .advantage-bar .your .animation:after { content: ""; display: block; position: absolute; left: auto; right: 0; top: 0; height: 5px; width: 15px; background: url(/images/v2/faction/wars/progress_bar_pointer.png) 0 -15px no-repeat } .advantage-bar .your .animation:after { background: #ff7a4d; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmN2E0ZCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNkOTM2MDAiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); background: -moz-linear-gradient(top,rgba(255,122,77,1) 0%,rgba(217,54,0,1) 100%); background: -webkit-linear-gradient(top,rgba(255,122,77,1) 0%,rgba(217,54,0,1) 100%); background: linear-gradient(to bottom,rgba(255,122,77,1) 0%,rgba(217,54,0,1) 100%); z-index: 5 } .advantage-bar .your .animation:before { z-index: 6 } .advantage-bar .your:nth-child(2) .animation:before { right: auto; left: 0; background: url(/images/v2/faction/wars/progress_bar_pointer_2.png) 0 -15px no-repeat } .advantage-bar .your:nth-child(2) .animation:after { right: auto; left: 0 } .advantage-bar .enemy .animation .arrows { background-position-y: 0; -webkit-animation: advantage-bar-animation 30s linear .1s infinite reverse; animation: advantage-bar-animation 30s linear .1s infinite reverse; z-index: 7 } .advantage-bar .enemy .animation:before, .advantage-bar .enemy .animation:after { content: ""; display: block; position: absolute; left: 0; top: 0; height: 5px; width: 15px; background: url(/images/v2/faction/wars/progress_bar_pointer.png) 0 0 no-repeat } .advantage-bar .enemy .animation:after { background: #a3d900; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2EzZDkwMCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiM2OThjMDAiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); background: -moz-linear-gradient(top,rgba(163,217,0,1) 0%,rgba(105,140,0,1) 100%); background: -webkit-linear-gradient(top,rgba(163,217,0,1) 0%,rgba(105,140,0,1) 100%); background: linear-gradient(to bottom,rgba(163,217,0,1) 0%,rgba(105,140,0,1) 100%); z-index: 5 } .advantage-bar .enemy .animation:before { z-index: 6 } .advantage-bar .enemy:nth-child(1) .animation:before { right: 0; left: auto; background: url(/images/v2/faction/wars/progress_bar_pointer_2.png) 0 0 no-repeat } .advantage-bar .enemy:nth-child(1) .animation:after { right: 0; left: auto } .advantage-bar .enemy:nth-child(1) .animation .arrows { left: -5px } @-webkit-keyframes advantage-bar-animation { 0% { background-position-x: 0 } 100% { background-position-x: 1000px } } @keyframes advantage-bar-animation { 0% { background-position-x: 0 } 100% { background-position-x: 1000px } } @keyframes advantage-bar-animation { 0% { background-position-x: 0 } 100% { background-position-x: 1000px } } `) var city = {} xhook.after(function(request, response) { if(request.url.match(/&step=mapData/)){ city = JSON.parse(response.data); waitForKeyElements(".leaflet-layer", build) } }) function build(){ if(!localStorage.hasOwnProperty("mapApi")){ $($('ul[class*="map-symbols"]')[0].previousElementSibling).append('<span id=apiAsk class="symPane" data-att=0 style="float:right; margin-right:10px;">Add api key</span>') $($('ul[class*="map-symbols"]')[0].previousElementSibling).append('<span id=apiAsk class="symPane" style="display:none; float:right; margin-right:10px;">Change api key</span>') }else{ $($('ul[class*="map-symbols"]')[0].previousElementSibling).append('<span id=apiAsk class="symPane" style="float:right; margin-right:10px;">Change api key</span>') } $('span[id*="apiAsk"]').on('click', function(){ var apiKey = localStorage.setItem("mapApi", prompt("Enter api key")) if(this.attributes[2].nodeValue == 0){ $('span[id*="apiAsk"]').toggle() } }) $($('ul[class*="map-symbols"]')[0].previousElementSibling).append(' - <span id="hideLeaf" class="symPane">Hide all</span>') $($('ul[class*="map-symbols"]')[0].previousElementSibling).append('<span id="hideLeaf" class="symPane" style="display:none">Show all</span>') if($('input[id*="view-switcher"]')[0].checked == true){ var racket = {} var territories = {} var items = sorti('title',JSON.parse(atob(city.territoryUserItems))) $('div[class*="territory-info-toggle"]').before('<div id=listcont><div id=listSelections class="title-black top-round m-top10"><span id=select name=items class="selectedtab">Items </span> / <span id=select name=rackets>Rackets </span> / <span id=select name=wars>Wars</span></div><table id=items name=table class="cont-gray10 bottom-round activetab"><thead class="tab"><tr><th id=type colspan=6>Items</th></tr><tbody id=idata class="tab" style="display:block;"></tbody></table><table id=rackets name=table class="cont-gray10 bottom-round inactive"><thead class="tab"><tr><th id=type>Type</th><th id=level>Level</th><th id=description>Description</th><th id=ter>Territory</th><th id=owner>Owner</th></tr></thead><tbody id=rdata class="tab"></tbody></table><table id=wars name=table class="cont-gray10 bottom-round inactive"><thead class="tab"><tr><th id=territory>Territory</th><th id=attacker>Attacker</th><th>Progress</th><th id=defender>Defender</th></tr></thead><tbody id=wdata class="tab"></tbody></table></div>') $.each(torn.get('collection'), function(i, v){ if(this.factionID){ racket[parseInt(this.path.attributes[0].nodeValue)] = [this.factionID] } else{ racket[parseInt(this.path.attributes[0].nodeValue)] = ['na'] } }) $.each(territories_shapes, function(i, v){ territories[this[4]] = this[9] }) $.each(standard_shapes, function(i, v){ territories[this[4]] = this[9] }) $.each(city.rackets, function(i, v){ racket[city.rackets[i].territoryID].push('true',city.rackets[i].typeName,city.rackets[i].level,city.rackets[i].description) $('tbody[id*="rdata"]').append('<tr><td>'+city.rackets[i].typeName+'</td><td>'+city.rackets[i].level+'</td><td>'+city.rackets[i].description+'</td><td><a id="flink" href="https://www.torn.com/city.php#terrName='+territories[city.rackets[i].territoryID]+'">'+territories[city.rackets[i].territoryID]+'</td><td>'+torn.getFactionName(racket[city.rackets[i].territoryID][0])+'</td></tr>') }) if(items.length > 0){ $.each(items, function(i, v){ $('tbody[id*="idata"]').append('<tr style="display:inline-block; border:none!important"><td class="cell" data-id='+[i]+' style="border: none!important">'+items[i].title+'</td></tr>') }) } if(localStorage.hasOwnProperty("mapApi") && localStorage.getItem("mapApi").length > 0 ){ var getapiData = new Promise((resolve, reject) => { var apiData = getAction({ type: "GET", action: "https://api.torn.com/torn/?selections=territorywars&key="+localStorage.getItem("mapApi"), }) resolve(apiData) }) Promise.all([getapiData]).then(([apiData]) => { if(apiData.hasOwnProperty("error")){ alert('Error code - '+apiData.error.code+" | "+apiData.error.error) }else{ $.each(city.factionOwnTerritories.terrInWars, function(i, v){ if(localStorage.getItem("mapApi").length > 0 && !apiData.hasOwnProperty("error")){ var cdt = new Date(JSON.parse(apiData)['territorywars'][territories[city.factionOwnTerritories.terrInWars[i].territoryID]].ends*1000).toLocaleString("en-US", {timeZone: 'UTC'}) $($('td[data-ids*="'+city.factionOwnTerritories.terrInWars[i].territoryID+'"]')).prepend('<div class="addTime" data-countdown="'+cdt+'"></div>') } }) } $('[data-countdown]').each(function() { var $this = $(this), finalDate = $(this).data('countdown'); $this.countd(finalDate, function(event) { $this.html(event.strftime('%D:%H:%M:%S')); }) }) }) } $.each(city.factionOwnTerritories.terrInWars, function(i, v){ if(racket[city.factionOwnTerritories.terrInWars[i].territoryID][1] == 'true'){ $('tbody[id*="wdata"]').append('<tr><td><span class=wImg><a id="flink" href="https://www.torn.com/city.php#terrName='+territories[city.factionOwnTerritories.terrInWars[i].territoryID]+'">'+territories[city.factionOwnTerritories.terrInWars[i].territoryID]+'</a><img id=rImg src="images/v2/city/citymap/rackets/street_surgeon_level_5.svg" title="<b>'+racket[city.factionOwnTerritories.terrInWars[i].territoryID][2]+' '+racket[city.factionOwnTerritories.terrInWars[i].territoryID][3]+'</b><br>'+racket[city.factionOwnTerritories.terrInWars[i].territoryID][4]+'"><td>'+torn.getFactionName(city.factionOwnTerritories.terrInWars[i].attackFaction)+'</td><td data-ids='+city.factionOwnTerritories.terrInWars[i].territoryID+'><div id=warProgress data-id='+city.factionOwnTerritories.terrInWars[i].territoryID+' style="margin-bottom: 1px">Show progress bar</div></td><td>'+torn.getFactionName(city.factionOwnTerritories.terrInWars[i].defendFaction)+'</span></td></tr>') }else{ $('tbody[id*="wdata"]').append('<tr><td><a id="flink" href="https://www.torn.com/city.php#terrName='+territories[city.factionOwnTerritories.terrInWars[i].territoryID]+'">'+territories[city.factionOwnTerritories.terrInWars[i].territoryID]+'</a><td>'+torn.getFactionName(city.factionOwnTerritories.terrInWars[i].attackFaction)+'</td><td data-ids='+city.factionOwnTerritories.terrInWars[i].territoryID+'><div id=warProgress data-id='+city.factionOwnTerritories.terrInWars[i].territoryID+' style="margin-bottom: 1px">Show progress bar</div></td><td>'+torn.getFactionName(city.factionOwnTerritories.terrInWars[i].defendFaction)+'</td></tr>') } }) $('div[id*="warProgress"]').on('click', function(){ var getUpdate = new Promise((resolve, reject) => { var update = getAction({ type: "POST", action: "war.php", data: { step: 'getWarInfo', terr: $(this)[0].attributes[1].nodeValue } }) resolve(update) }) Promise.all([getUpdate]).then(([update]) => { if($($(this)[0].parentNode).find('.addTime').length == 1){ $($(this)[0].parentNode).find('.addTime').wrap('<div id="terrIcons" style="display: flex; align-items: center"></div>') $($(this)[0].parentNode).find('#terrIcons').prepend('<div style="display: inline-flex; align-items: center; min-width: 37px"><i class="faction-attacking"></i>'+JSON.parse(update).count1+'</div>') $($(this)[0].parentNode).find('#terrIcons').append('<div style="display: inline-flex; align-items: center; min-width: 37px; justify-content: flex-end">'+JSON.parse(update).count2+'<i class="faction-defending" style="background-position-y: -9px;"></i></div>') }else{ $($(this)[0].parentNode).prepend('<div id="terrIcons" style="display: flex; align-items: center"><div style="display: inline-flex; align-items: center; min-width: 37px"><i class="faction-attacking"></i>'+JSON.parse(update).count1+'</div><div class="addTime"></div><div style="display: inline-flex; align-items: center; min-width: 37px; justify-content: flex-end">'+JSON.parse(update).count2+'<i class="faction-defending" style="background-position-y: -9px;"></i></div></div>') } var diff = Math.abs(JSON.parse(update).count1 - JSON.parse(update).count2); var arrowSpeed = (diff >= 5 ? 'fast' : (diff >= 3 ? 'medium' : (diff >= 1 ? 'slow' : ''))) $($(this)[0].parentNode).append('<div class="advantage-bar" data-terrId='+$(this)[0].attributes[1].nodeValue+'><div id=eAdd class="enemy" style="width: '+JSON.parse(update).scorePercent+'%"></div><div id=yAdd class="your"></div></div>') if(JSON.parse(update).count1 > JSON.parse(update).count2){ $($('div[data-terrId*="'+$(this)[0].attributes[1].nodeValue+'"]')[0].children[0]).append('<div class="animation '+arrowSpeed+'"><div class="arrows"></div></div>') }else{ $($('div[data-terrId*="'+$(this)[0].attributes[1].nodeValue+'"]')[0].children[1]).append('<div class="animation '+arrowSpeed+'"><div class="arrows"></div></div>') } $(this).remove() }) }) $('span[id*="select"]').on('click', function(){ $('span[id*="select"]').removeClass('selectedtab') $(this).addClass('selectedtab') $('table[name*="table"]').removeClass('activetab').addClass('inactive'); $('table[id*="'+$(this)[0].attributes[1].nodeValue+'"]').addClass('activetab').removeClass('inactive') }) document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => { sort(th) }))) $('a[id*="flink"]').on('click', function(){ document.documentElement.scrollTop = 0 }) $('tbody[id*="idata"]').find('td').on('click', function(){ var td = $(this) const XHR = new XMLHttpRequest(), data = new FormData(); data.append('td', btoa(items[$(this)[0].attributes[1].nodeValue].c['x']+'O'+items[$(this)[0].attributes[1].nodeValue].c['y']+'O'+items[$(this)[0].attributes[1].nodeValue].id+'O'+items[$(this)[0].attributes[1].nodeValue].ts)); data.append('step', 'uif'); XHR.addEventListener('load', function(event) { if(JSON.parse(event.target.response)['success'] == true){ td[0].textContent = td[0].textContent+' Picked Up' td.css("color","#008000") setTimeout(() => {td[0].parentElement.remove(); }, 2000); } }); XHR.addEventListener('error', function(event) { console.log(event); }); XHR.open('POST', 'https://www.torn.com/city.php?rfcv=undefined'); XHR.setRequestHeader('X-Requested-With', 'XMLHttpRequest') XHR.send(data); }) sort(document.querySelector("#level")) }else{ $('div[class*="territory-info-toggle"]').before('<div id=alert><div class="white-grad border-round m-top10 p10">Enable full map and refresh for lists</div>') } $('span[id*="hideLeaf"]').on('click', function(){ $('img[class*="standard-object-marker"]').toggle(); $('span[id*="hideLeaf"]').toggle() }) } function sorti (prop, arr) { prop = prop.split('.'); var len = prop.length; arr.sort(function (a, b) { var i = 0; while( i < len ) { a = a[prop[i]]; b = b[prop[i]]; i++; } if (a < b) { return -1; } else if (a > b) { return 1; } else { return 0; } }); return arr; }; function sort(th){ const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent; const comparer = (idx, asc) => (a, b) => ((v1, v2) => v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2))(getCellValue(asc ? b : a, idx), getCellValue(asc ? a : b, idx)); const table = th.closest('table'); const tbody = table.querySelector('tbody'); Array.from(tbody.querySelectorAll('tr')) .sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc)) .forEach(tr => tbody.appendChild(tr) ); }