【问题标题】:Sorting a table in JavaScript - hiding table rows with no subsequent rows在 JavaScript 中对表格进行排序 - 隐藏没有后续行的表格行
【发布时间】:2021-12-06 00:22:08
【问题描述】:

我有一个我用 JavaScript 构建的表格,它基本上是一个很大的出版物列表,带有一个“年份”标题,然后是该年份的每个出版物的一行:

  <table class="mytable" id="myTable">
  <tr class="header">
    <th style="width:60%;">2020</th>
  </tr>
  <tr name="latin_america_zero-deforestation">
    <td>"Cattle Ranchers and Deforestation.”
    </td>
  </tr>
  <tr name="latin_america_policy-outcomes">
    <td>“Impacts on Land Use Conversion.”
    </td>
  </tr>
  <tr name="latin_america_supply-chain_policy-outcomes">
    <td>“Costs of the Amazon Soy Moratorium.”
    </td>
  </tr>
  <tr class="header">
    <th style="width:60%;">2019</th>
  </tr>
  <tr name="africa_policy-outcomes">
    <td>“Environmental Change”
    </td>
  </tr>
  <tr name="latin_america_policy-outcomes">
    <td>“Land Use Change”
    </td>
  </tr>
  <tr class="header">
    <th style="width:60%;">2018</th>
  </tr>
  <tr name="north_america_zero-deforestation">
    <td>“Deforestation Remedies”
    </td>
  </tr>
  <tr name="latin_america_zero-deforestation">
    <td>“Land Use Change in Latin American Cerrados”
    </td>
  </tr>
</table>  

基于tr 名称,我可以通过一些按钮按关键字(地区或主题)进行过滤:

  <button class="btn" onclick="filterSelection('policy-outcomes')">Policy Outcomes</button>

  <button class="btn" onclick="filterSelection('latin_america')"> Latin America</button>

按钮调用此函数,该函数根据tr 名称标签中的关键字进行过滤:

//Sort by class..
function filterSelectionByTopic(thing) {

  var rows = $('#myTable').find('tr:not(:has(th))').get();
  console.log(rows)

$("#myTable tr").show()


$('#myTable').find('tr:not(:has(th))').not('thead td').not("[name*=" + thing + "]").fadeOut(300)

var tabhead = "header"
//$("#myTable tbody tr:not(."+thing + ')').hide();

//$("#myTable tr.header").fadeIn(1)

}

虽然这工作正常,但我希望过滤器隐藏没有返回行的表头(年份)。

例如,如果我使用 filterSelection('policy-outcomes') 进行过滤:2020 年和 2019 年有符合此条件的出版物,而 2018 年则没有。目前 2018 将显示为表头,下面没有行,但我想隐藏任何没有符合条件的行的表头。

所以,本质上,filterSelection('policy-outcomes') 应该是这样的:

2020
Impacts on Land Use Conversion.
Costs of the Amazon Soy Moratorium.
2019
Environmental Change.
Land Use Change.

我怎样才能做到这一点?

【问题讨论】:

  • 您的 HTML 错误:TR 元素不接受 name 属性,您应该改用数据集属性
  • 这能回答你的问题吗? How to sort an HTML table?

标签: javascript html


【解决方案1】:

您可以将 :has 选择器与相邻的兄弟选择器 (s1 + s2) 结合使用。 因此,请执行以下操作:

$("#myTable").find("tr.header:has(+ tr.header)").hide()

【讨论】:

    【解决方案2】:

    一般是not a good idea 使用内联事件处理器。 sn-p 使用 event delegation 和普通的 es-20xx。

    按钮包含一个data-term-属性,可以从中构造data-name-rows 的css 查询选择器。 name-attributes 替换为 data-name-attributes,用逗号分隔值,对应于按钮中的 data-term-attributes。

    在“年份”行中,我添加了一个data-year-属性,以便能够轻松地从data-name 行中找到标题。

    处理程序:

    • 使用从单击按钮的data-term 属性派生的正则表达式过滤所有行
    • 如果找到一个元素,显示它,找到前一个标题并显示它

    sn-p 也可用in Stackblitz

    const doFilter = FilterHandlerFactory();
    document.addEventListener(`click`, handle);
    
    function handle(evt) {
      if (evt.target.dataset.term) {
        return  doFilter(evt.target);
      }
    }
    
    // Factory to avoid recreation of [showParentHeader] on every click
    function FilterHandlerFactory() {
      const isHead = r => r.dataset.year && r;
      // helper (closed over):
      // move up until a header is found and display that
      const showParentHeader = row => {
        let prev = row.previousElementSibling;
        while (prev && !isHead(prev)) {  prev = prev.previousElementSibling; }
        prev.classList.remove(`hidden`);
      };
    
      return fromBtn => {
        const all = fromBtn.dataset.term === `All`;
        const filter = RegExp(fromBtn.dataset.term, "i");
        document.querySelectorAll(`#myTable tr`)
          .forEach( row => {
            if (all || filter.test(row.dataset.name)) {
              row.classList.remove(`hidden`);
              return all || showParentHeader(row);
            }
    
            row.classList.add(`hidden`);
          } );
      }
    }
    body {
      font: normal 12px/15px verdana, arial;
    }
    
    #myTable {
      border: 1px solid #999;
      border-collapse: separate;
      margin-top: 1rem;
      width: 80vw;
    }
    
    #myTable th {
      background-color: #ddd;
      border-bottom: 1px dotted #777;
      border-top: 1px dotted #777;
    }
    
    button {
      margin-right: 2px;
    }
    
    button[data-term]:before {
      content: attr(data-term);
    }
    
    .hidden {
      display: none;
    }
    
    .btnBlock {
      display: flex;
      flex-direction: row; 
    }
    <div class="btnBlock">
      <button data-term="Policy Outcomes"></button>
      <button data-term="Latin America"></button>
      <button data-term="Deforestation"></button>
      <button data-term="Supply Chain"></button>
      <button data-term="Africa"></button>
      <button data-term="North America"></button>
      <button data-term="All"></button>
    </div>
    <table class="mytable" id="myTable">
      <tr class="header" data-year="2020">
        <th>2020</th>
      </tr>
      <tr data-name="latin america, deforestation">
        <td>"Cattle Ranchers and Deforestation.”</td>
      </tr>
      <tr data-name="latin america, policy outcomes">
        <td>“Impacts on Land Use Conversion.”</td>
      </tr>
      <tr data-name="latin america, supply chain, policy outcomes">
        <td>“Costs of the Amazon Soy Moratorium.”</td>
      </tr>
      <tr class="header" data-year="2019">
        <th>2019</th>
      </tr>
      <tr data-name="africa, policy outcomes">
        <td>“Environmental Change”</td>
      </tr>
      <tr data-name="latin america, policy outcomes">
        <td>“Land Use Change”</td>
      </tr>
      <tr class="header" data-year="2018">
        <th>2018</th>
      </tr>
      <tr data-name="north america, deforestation">
        <td>“Deforestation Remedies”</td>
      </tr>
      <tr data-name="latin america, deforestation">
        <td>“Land Use Change in Latin American Cerrados”</td>
      </tr>
    </table>

    或者,考虑使用&lt;table&gt;(数据毕竟不是真正的表格)。

    Stackblitz playground 为这个 sn-p。

    document.addEventListener(`click`, handle);
    const doFilter = FilterHandlerFactory(`myData`);
    
    function handle(evt) {
      if (evt.target.dataset.term) {
        return  doFilter(evt.target);
      }
    }
    
    // filter handler factory
    function FilterHandlerFactory(filterId) {
      let filterTerm, all;
      const filterYears = document.querySelectorAll(`#${filterId} div[data-year]`);
      const filterItems = document.querySelectorAll(`#${filterId} ul li`);
      const year = item => item.closest(`[data-year]`);
      const hide = el => el.classList.add(`hidden`);
      const show = el => el.classList.remove(`hidden`);
      const displayFilteredItem = item => { show(item); show(year(item)); };
      const setTerms = fromBtn => {
        all = fromBtn.dataset.term === `All`;
        filterTerm = RegExp(fromBtn.dataset.term, "i");
      };
      const filter = item => all || filterTerm.test(item.dataset.name || '-') 
          ? displayFilteredItem(item)
          : hide(item);
    
      return fromBtn => {
        setTerms(fromBtn);
        filterYears.forEach( hide )
        filterItems.forEach( filter );
      };
    }
    body {
      margin: 2rem;
      font: normal 12px/15px verdana, arial;
    }
    
    #myData {
      margin-top: 1rem;
      width: 80vw;
    }
    
    #myData div[data-year] {
      opacity: 1;
      max-height: initial;
      transition: all ease-in-out .7s;
    }
    
    #myData div[data-year].hidden {
      opacity: 0;
      max-height: 0;
      margin: 0;
    }
    
    #myData div[data-year] ul li.hidden {
      display: none;
    }
    
    button {
      margin-right: 2px;
    }
    
    button[data-term]:before {
      content: attr(data-term);
    }
    
    .btnBlock {
      display: flex;
      flex-direction: row;
    }
    
    #myData div[data-year] ul {
      margin-left: -1.5rem;
      margin-top: 0.2rem;
    }
    
    #myData div[data-year] ul li {
      list-style-type: '\2713';
      padding-left: 5px;
    }
    
    #myData div[data-year]:before {
      content: attr(data-year);
      text-align: center;
      font-weight: bold;
      display: block;
      width: 100%;
      background-color: #ddd;
      border-bottom: 1px dotted #777;
      border-top: 1px dotted #777;
    }
    <div class="btnBlock">
      <button data-term="Policy Outcomes"></button>
      <button data-term="Latin America"></button>
      <button data-term="Deforestation"></button>
      <button data-term="Supply Chain"></button>
      <button data-term="Africa"></button>
      <button data-term="North America"></button>
      <button data-term="All"></button>
    </div>
    
    <div id="myData">
    
      <div data-year="2020">
        <ul>
          <li data-name="latin america, deforestation">
            Cattle Ranchers and Deforestation.
          </li>
          <li data-name="latin america, policy outcomes">
            Impacts on Land Use Conversion.
          </li>
          <li data-name="latin america, supply chain, policy outcomes">
            Costs of the Amazon Soy Moratorium.
          </li>
        </ul>
      </div>
    
      <div data-year="2019">
        <ul>
          <li data-name="africa, policy outcomes">Environmental Change</li>
          <li data-name="latin america, policy outcomes">Land Use Change</li>
        </ul>
      </div>
    
      <div data-year="2018">
        <ul>
          <li data-name="north america, deforestation">
            Deforestation Remedies
          </li>
          <li data-name="latin america, deforestation">
            Land Use Change in Latin American Cerrados
          </li>
        </ul>
      </div>
    
    </div>

    【讨论】:

      【解决方案3】:

      这是我的 Vanilla JavaScript 版本。

      在编写脚本之前,我将所有name 属性更改为data-name,因为trs 不能“合法”拥有name 属性。

      在第一个filter() 循环中,我使所有trs 不可见,并仅过滤掉那些与给定模式pat 不匹配且不属于“标题”类的trs。在应用于此过滤集的forEach() 循环中,我然后仅使那些自身具有dataset.name 属性或其直接后继具有此属性的tr 再次可见。

      const trs=[...document.querySelector("#myTable>tbody").children];
      function fltTbl(pat){
        trs
         .filter(tr=>(tr.style.display="none",tr.classList.contains("header")||
      tr.dataset?.name.indexOf(pat)>-1))
         .forEach((tr,i,a)=>{
        if (tr.dataset.name
            || a[i+1]?.dataset.name)
          tr.style.display="";
       });
      }
      <button class="btn" onclick="fltTbl('policy-outcomes')">Policy Outcomes</button>
      <button class="btn" onclick="fltTbl('latin_america')">Latin America</button>
      <button class="btn" onclick="fltTbl('deforestation')">Deforestation</button>
      
      <table class="mytable" id="myTable">
        <tr class="header">
      <th style="width:60%;">2020</th>
        </tr>
        <tr data-name="latin_america_zero-deforestation">
      <td>"Cattle Ranchers and Deforestation.”
      </td>
        </tr>
        <tr data-name="latin_america_policy-outcomes">
      <td>“Impacts on Land Use Conversion.”
      </td>
        </tr>
        <tr data-name="latin_america_supply-chain_policy-outcomes">
      <td>“Costs of the Amazon Soy Moratorium.”
      </td>
        </tr>
        <tr class="header">
      <th style="width:60%;">2019</th>
        </tr>
        <tr data-name="africa_policy-outcomes">
      <td>“Environmental Change”
      </td>
        </tr>
        <tr data-name="latin_america_policy-outcomes">
      <td>“Land Use Change”
      </td>
        </tr>
        <tr class="header">
      <th style="width:60%;">2018</th>
        </tr>
        <tr data-name="north_america_zero-deforestation">
      <td>“Deforestation Remedies”
      </td>
        </tr>
        <tr data-name="latin_america_zero-deforestation">
      <td>“Land Use Change in Latin American Cerrados”
      </td>
        </tr>
      </table>

      【讨论】:

      • Carsten- 非常感谢!这行得通。现在我只需要尝试理解你的代码..谢谢!
      • 对不起 - 赏金已过期!我有一段时间没有检查过,但今天检查了一下,您的解决方案有效。
      【解决方案4】:

      您可以将:has 选择器与adjacent sibling selector (s1 + s2) 结合使用。
      因此,请执行以下操作:

      $("#myTable").find("tr.header:has(+ tr.header)").hide()
      

      这将选择紧跟另一个标题行的每个标题行 - 即,每个标题都没有至少一行与之关联

      我猜想使用:last-child 之类的东西来捕捉最后一个标题不返回任何行的边缘情况。

      【讨论】:

      • 射击。我已经尝试过: $("#myTable").find("tr.header:has(+ tr.header)").hide() 仍然没有运气..它不会隐藏标题,然后是另一个标题。
      【解决方案5】:

      解决方案

      此解决方案遍历每一行并保存标题。然后它会查找其后面与搜索词的正则表达式匹配的数据行。如果在到达下一个标头之前,标头后面没有数据行,它将对标头节点应用无样式。

      示例

      以下示例设置了一个非洲搜索词,然后在 4 秒后应用了一个拉丁搜索词。

      //Sort by class..
      function filterSelectionByTopic(thing) {
        $("#myTable tr").show()
      
        $('#myTable').find('tr:not(:has(th))').not('thead td').not("[name*=" + thing + "]").fadeOut(300)
      
      const allRows = Array.from($('#myTable').find('tr'));
      let currentHeaderNode = null;
      let visibleRows = 0;
      allRows.forEach((row, index,{ length })=>{
        // reset display none from last search
        if (row.classList.contains("header"))
          row.style.display=undefined;
        if (row.classList.contains("header") || index===length-1) {
          if (!visibleRows && currentHeaderNode !== null) {
            currentHeaderNode.style.display="none";
          }
          // header to target if no sibling data rows
          currentHeaderNode = row;
          visibleRows = 0;
          return
        }
        if (new RegExp(thing,"gi").test(row.getAttribute("name"))) {
          // There IS a row that will be shown (don't hide header)
          visibleRows++;
        }
      })
      }
      
      filterSelectionByTopic("africa")
      
      setTimeout(()=>{filterSelectionByTopic("latin")},4000)
      th {
        background-color:red;
      }
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
      <table class="mytable" id="myTable">
        <tr class="header">
          <th style="width:60%;">2020</th>
        </tr>
        <tr name="latin_america_zero-deforestation">
          <td>"Cattle Ranchers and Deforestation.”
          </td>
        </tr>
        <tr name="latin_america_policy-outcomes">
          <td>“Impacts on Land Use Conversion.”
          </td>
        </tr>
        <tr name="latin_america_supply-chain_policy-outcomes">
          <td>“Costs of the Amazon Soy Moratorium.”
          </td>
        </tr>
        <tr class="header">
          <th style="width:60%;">2019</th>
        </tr>
        <tr name="africa_policy-outcomes">
          <td>“Environmental Change”
          </td>
        </tr>
        <tr name="latin_america_policy-outcomes">
          <td>“Land Use Change”
          </td>
        </tr>
        <tr class="header">
          <th style="width:60%;">2018</th>
        </tr>
        <tr name="north_america_zero-deforestation">
          <td>“Deforestation Remedies”
          </td>
        </tr>
        <tr name="latin_america_zero-deforestation">
          <td>“Land Use Change in Latin American Cerrados”
          </td>
        </tr>
      </table>

      【讨论】:

        【解决方案6】:

        这个solution 从最后一行开始,沿着 DOM 树向上走。如果表格行没有术语,则将其隐藏。我还添加了一个清除按钮。

        function filterSelection(topic) {
          const index = {};
          const HIDE_CLASS = 'policy-no-term';
          let tr = document.querySelector('tr:last-child');
          let hasTerms = false;
          
          while(tr) {
             tr.classList.remove(HIDE_CLASS);
            
             if (tr.classList.contains('header')) {
               if (hasTerms === false) {
                 tr.classList.add(HIDE_CLASS);
               }
               hasTerms = false;
             } else if(tr.getAttribute('name').indexOf(topic) > -1) {
               hasTerms = true;
               tr.classList.remove(HIDE_CLASS);
             } else {
               hasTerms = hasTerms || false;
               tr.classList.add(HIDE_CLASS);
             }
             tr = tr.previousElementSibling;
          }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-01-23
          • 2012-05-19
          • 1970-01-01
          • 1970-01-01
          • 2014-12-12
          • 1970-01-01
          • 2021-12-21
          • 2012-12-25
          相关资源
          最近更新 更多