Вот пример, который не приводит к нулю -1 и не полагается на трюк до infty, который по-прежнему позволяет ставить нулевые значения в конец.
- нагрузка loadData должна отражать вашу json (я скопировал с него)
- обработчик сортировки, возможно, сохраняет последнюю сортировку (asc / decs) на самом th-ом. Очевидно, вы можете рассматривать массив в качестве альтернативы, если вам нравится
Прямой путь
- Мы храним строки в памяти
- Сортируем строки ( в памяти)
- Мы переназначим таблицу, воссоздав все tr / tds
function loadData () {
return fetch('https://brendonbrewer.com/lbrynomics/subscriber_counts.json').then(r => r.json())
return Promise.resolve({
rank_change: [null, null, 5, 6, 8, 2],
views_change: [null, 5, null, 6, 7, 2],
})
}
let rows = []
const myTable = document.querySelector('table')
function render(myTable, rows) {
myTable.tBodies[0].innerHTML = ''
rows.forEach(row => {
const tr = document.createElement('tr')
row.forEach(f => {
const td = document.createElement('td')
td.innerText = f == null ? '--' : f
tr.appendChild(td)
})
myTable.tBodies[0].appendChild(tr)
})
}
loadData().then(iRows => {
// store the fields in order of your DOM nodes
rows = iRows.views_change.map((_,i) => [iRows.views_change[i], iRows.rank_change[i]])
render(myTable, rows)
})
// sort the rows
document.querySelector('table thead').onclick = e => {
const th = e.target
if (th.nodeName !== 'TH') { return }
const order = th.getAttribute('data-sort') === '-1' ? -1 : 1
th.setAttribute('data-sort', order * -1)
const fieldIndex = [...th.parentNode.children].findIndex(other => other === th)
const cmp = (r, s) => {
const a = r[fieldIndex]
const b = s[fieldIndex]
return a === null && b === null ? 0 :
a === null && b !== null ? 1 :
b === null && a !== null ? -1 :
a - b
}
rows.sort(order === 1 ? cmp : (a,b) => -1 * cmp(a,b))
console.time('render')
render(myTable, rows)
console.timeEnd('render')
}
<table id="sub-stats">
<thead> <!-- first fix your th. They go to thead. -->
<tr>
<th class="views-change">Views Change</th>
<th class="rank-change">Rank Change</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
То же самое, но мы повторно используем существующие строки
Мы в основном сопоставляем каждую строку в памяти с ее узлом DOM (то есть tr
) , См rowToNode
в коде ниже
function loadData () {
return fetch('https://brendonbrewer.com/lbrynomics/subscriber_counts.json').then(r => r.json())
return Promise.resolve({
rank_change: [null, null, 5, 6, 8, 2],
views_change: [null, 5, null, 6, 7, 2],
})
}
let rows = []
let rowToNode = new Map()
const myTable = document.querySelector('table')
function init(myTable, rows) {
const rowToNode = new Map()
rows.forEach(row => {
const tr = document.createElement('tr')
row.forEach(f => {
const td = document.createElement('td')
td.innerText = f == null ? '--' : f
tr.appendChild(td)
})
myTable.tBodies[0].appendChild(tr)
rowToNode.set(row, tr)
})
return rowToNode
}
loadData().then(iRows => {
// store the fields in order of your DOM nodes
rows = iRows.views_change.map((_,i) => [iRows.views_change[i], iRows.rank_change[i]])
rowToNode = init(myTable, rows)
})
// sort the rows
document.querySelector('table thead').onclick = e => {
const th = e.target
if (th.nodeName !== 'TH') { return }
const order = th.getAttribute('data-sort') === '-1' ? -1 : 1
th.setAttribute('data-sort', order * -1)
const fieldIndex = [...th.parentNode.children].findIndex(other => other === th)
const cmp = (r, s) => {
const a = r[fieldIndex]
const b = s[fieldIndex]
return a === null && b === null ? 0 :
a === null && b !== null ? 1 :
b === null && a !== null ? -1 :
a - b
}
rows.sort(order === 1 ? cmp : (a,b) => -1 * cmp(a,b))
console.time('render')
myTable.tBodies[0].innerHTML = ''
const tpl = new DocumentFragment()
rows.forEach(r => {
tpl.appendChild(rowToNode.get(r))
})
myTable.tBodies[0].appendChild(tpl)
console.timeEnd('render')
}
<!DOCTYPE html>
<html>
<body>
<table id="sub-stats">
<thead> <!-- first fix your th. They go to thead. -->
<tr>
<th class="views-change">Views Change</th>
<th class="rank-change">Rank Change</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</body>
</html>
Немного рефакторинга
Глобальные переменные не самые лучшие, надоедливые переменные rows
и связанные с ними rowToNode
не являются более гламурный и получит область, большую, чем им нужно.
Возможный способ - создать Компонент, который внутренне управляет этими переменными, и в конечном итоге поместить его в свой собственный файл. Будучи немного напуганным, мы могли бы написать WebComponent
class MyTable extends HTMLTableElement {
constructor () {
super ()
this._shadowRoot = this.attachShadow({ 'mode': 'open' });
this._rows = []
this.rowToNode = new Map()
const template = document.createElement('template')
template.innerHTML = `
<thead>
<tr>
<th class="views-change">Views Change</th>
<th class="rank-change">Rank Change</th>
</tr>
</thead>
<tbody>
</tbody>
<style>th{cursor:pointer;}</style>
`
this._shadowRoot.appendChild(template.content)
this.tbody = this._shadowRoot.querySelector('tbody')
this.render();
this._shadowRoot.querySelector('thead').onclick = this.handleSort.bind(this)
}
loadRows (rows) {
this.rowToNode = new Map()
this._rows = rows
rows.forEach(row => {
const tr = document.createElement('tr')
row.forEach(f => {
const td = document.createElement('td')
td.innerText = f == null ? '--' : f
tr.appendChild(td)
})
this.tbody.appendChild(tr)
this.rowToNode.set(row, tr)
})
}
render () {
this.tbody.innerHTML = ''
const tpl = new DocumentFragment()
this._rows.forEach(r => {
tpl.appendChild(this.rowToNode.get(r))
})
this._shadowRoot.querySelector('tbody').appendChild(tpl)
}
handleSort (e) {
const th = e.target
if (th.nodeName !== 'TH') { return }
const order = th.getAttribute('data-sort') === '-1' ? -1 : 1
th.setAttribute('data-sort', order * -1)
const fieldIndex = [...th.parentNode.children].findIndex(other => other === th)
const cmp = (r, s) => {
const a = r[fieldIndex]
const b = s[fieldIndex]
return a === null && b === null ? 0 :
a === null && b !== null ? 1 :
b === null && a !== null ? -1 :
a - b
}
this._rows.sort(order === 1 ? cmp : (a,b) => -1 * cmp(a,b))
console.time('render')
this.render()
console.timeEnd('render')
}
}
customElements.define('my-table', MyTable, { extends: 'table' })
function loadData () {
return fetch('https://brendonbrewer.com/lbrynomics/subscriber_counts.json').then(r => r.json())
return Promise.resolve({
rank_change: [null, null, 5, 6, 8, 2],
views_change: [null, 5, null, 6, 7, 2],
})
}
loadData().then(iRows => {
// store the fields in order of your DOM nodes
rows = iRows.views_change.map((_,i) => [iRows.views_change[i], iRows.rank_change[i]])
document.querySelector('my-table').loadRows(rows)
})
<my-table id="sub-stats"></my-table>
Альтернатива веб-компонентам
Наконец, если нам нужны не веб-компоненты, а сырой объект, мы можем создать сырой объект. Создайте некоторый класс, который принимает (табличный) узел в качестве аргумента, и работайте с ним так же, как мы это делали.
Ниже кода ближе (по DOM) к живому производству.
class MyTable {
constructor (node) {
this.node = node
this._rows = []
this.rowToNode = new Map()
const template = document.createElement('template')
template.innerHTML = `
<thead>
<tr>
<th class="ranks">Channel rank</th>
<th class="vanity_names">Top 200 LBRY Channels</th>
<th class="subscribers">followers</th>
<th class="views">Content view</th>
<th class="views_change">Views Change</th>
<th class="rank_change">Rank Change</th>
</tr>
</thead>
<tbody>
</tbody>
`
this.node.appendChild(template.content)
this.tbody = this.node.querySelector('tbody')
this.render();
this.node.querySelector('thead').onclick = this.handleSort.bind(this)
}
loadRows (rows) {
this.rowToNode = new Map()
this._rows = rows
rows.forEach(row => {
const tr = document.createElement('tr')
row.forEach(f => {
const td = document.createElement('td')
td.innerText = f == null ? '--' : f
tr.appendChild(td)
})
this.tbody.appendChild(tr)
this.rowToNode.set(row, tr)
})
}
render () {
this.tbody.innerHTML = ''
const tpl = new DocumentFragment()
this._rows.forEach(r => {
tpl.appendChild(this.rowToNode.get(r))
})
this.tbody.appendChild(tpl)
}
handleSort (e) {
const th = e.target
if (th.nodeName !== 'TH') { return }
const order = th.getAttribute('data-sort') === '-1' ? -1 : 1
th.setAttribute('data-sort', order * -1)
const fieldIndex = [...th.parentNode.children].findIndex(other => other === th)
const cmp = (r, s) => {
const a = r[fieldIndex]
const b = s[fieldIndex]
return a === null && b === null ? 0 :
a === null && b !== null ? 1 :
b === null && a !== null ? -1 :
a - b
}
this._rows.sort(order === 1 ? cmp : (a,b) => -1 * cmp(a,b))
console.time('render')
this.render()
console.timeEnd('render')
}
}
function loadData () {
return fetch('https://brendonbrewer.com/lbrynomics/subscriber_counts.json').then(r => r.json())
return Promise.resolve({
rank_change: [null, null, 5, 6, 8, 2],
views_change: [null, 5, null, 6, 7, 2],
})
}
console.log('go')
const table = new MyTable(document.querySelector('table'))
table.render()
loadData().then(iRows => {
// store the fields in order of your DOM nodes
const fields = ['ranks', 'vanity_names', 'subscribers', 'views', 'views_change', 'rank_change']
rows = iRows.views_change.map((_,i) => fields.map(f => iRows[f][i]))
console.log('go')
table.loadRows(rows)
})
<link href="https://lbrynomics.com/wp-content/themes/Divi/style.dev.css" rel="stylesheet"/>
<body class="page-template-default page page-id-12177 custom-background et-tb-has-template et-tb-has-footer et_pb_button_helper_class et_fullwidth_nav et_fixed_nav et_show_nav et_primary_nav_dropdown_animation_fade et_secondary_nav_dropdown_animation_fade et_header_style_left et_cover_background et_pb_gutter windows et_pb_gutters3 et_pb_pagebuilder_layout et_smooth_scroll et_no_sidebar et_divi_theme et-db gecko">
<div id="et-main-area">
<div id="main-content">
<div class="entry-content">
<div class="et-l et-l--post">
<div class="et_builder_inner_content et_pb_gutters3">
<div class="et_pb_section et_pb_section_0 et_pb_with_background et_section_regular">
<div class="et_pb_row et_pb_row_0">
<div class="et_pb_column et_pb_column_4_4 et_pb_column_0 et_pb_css_mix_blend_mode_passthrough et-last-child">
<div class="et_pb_module et_pb_code et_pb_code_0">
<div class="et_pb_module et_pb_tabs et_pb_tabs_0 et_slide_transition_to_1">
<div class="et_pb_tab et_pb_tab_1 clearfix et-pb-active-slide">
<div class="et_pb_tab_content">
<div class="et_pb_section et_pb_section_4 et_pb_with_background et_section_regular">
<div class="et_pb_row et_pb_row_3">
<div class="et_pb_column et_pb_column_4_4 et_pb_column_3 et_pb_css_mix_blend_mode_passthrough et-last-child">
<div class="et_pb_module et_pb_code et_pb_code_1">
<div class="et_pb_code_inner">
<table id="sub-stats"></table>
<div/>
<div/>
<div/>
<div/>
<div/>
<div/>
<div/>
<div/>
<div/>
<div/>
<div/>
<div/>
<div/>
<div/>
<div/>
<div/>
<div/>
</body>