如何根据分组行添加新的 <th>?

How do I add a new <th> based on grouped rows?

我不确定如何根据每个分组 table 创建自定义 header。

目前 table 组合在一起,但它只显示 1 行,带有一个你几乎看不到的加号。

我想尝试做的是用第一个 td 项目的 header 名称替换行信息以显示该行中有多少项目。

我有什么我正在尝试做什么的例子

What I currently have

What I would like it to do

//Making timesheet <td>
fetch(timehsheetUrl + date)
    .then(response => response.json())
    .then(data => makeTable(data.contracts))
    .catch(error => console.log(`Oh no! ${error}`))
const makeTable = (contracts) => {
    console.log(contracts);
    const body = document.getElementById('tsdata');
    contracts.forEach((person, index) => {
        person.details.forEach((entry) => {
            const dateString = entry.timesheetDate;
            const dateObject = new Date(dateString);
            const dd = dateObject.getDate();

            const mm = dateObject.toLocaleString('en-us', {
                month: 'short'
            });
            const yyyy = dateObject.getFullYear();
            const chartDate = `${dd}-${mm}-${yyyy}`
            console.log(chartDate);

            const htmlTemplate = `
    <tr class = "testDeleteTimesheet">
      <td>${entry.contractCode}</td>
      <td class="timesheetDetailsID" style="display:none;">${entry.timesheetDetailsId}</td>
      <td>${entry.activityCode}</td>
      <td>${entry.otFlag}</td>
      <td>${entry.notes}</td>
<td>${chartDate}</td>
<td class = "totalHours">${entry.hours}</td>
<td><button id=\"edit-" + counter + "\" class=\"btn editRow btnStyle btn-primary btn-sm\"><span class=\ "bi bi-pencil\"></span></button> 
<button id=\"delete-" + counter + "\" class=\"btn delRow btnStyle btn-danger btn-sm\"><span class=\"bi bi-eraser\"></span></button></td>
    </tr>
    `;

            body.innerHTML += htmlTemplate;
        });
        //Making timesheet easier to read

const tables = $('table')[0];
const rowGroups = {};
//loop through the rows excluding the first row (the header row)
while(tables.rows.length > 1){
    const row = tables.rows[1];
    const id = $(row.cells[0]).text();
    if(!rowGroups[id]) rowGroups[id] = [];
    if(rowGroups[id].length > 0){
        row.className = 'subrow';
        $(row).slideUp();
    }
    rowGroups[id].push(row);
    tables.deleteRow(1);
}
//loop through the row groups to build the new table content
for(let id in rowGroups){
    const group = rowGroups[id];
    for(let j = 0; j < group.length; j++){
        const row = group[j];
        if(group.length > 1 && j == 0) {
            //add + button
            const lastCell = row.cells[row.cells.length - 1];           
            $("<span class='collapsed'>").appendTo(lastCell).click(plusClick);
            
        }
        tables.tBodies[0].appendChild(row);        
    }
}
//function handling button click
function plusClick(e){
    const collapsed = $(this).hasClass('collapsed');
    const fontSize = collapsed ? 14 : 0;
    $(this).closest('tr').nextUntil(':not(.subrow)').slideToggle(400)
           .css('font-size', fontSize);
    $(this).toggleClass('collapsed');  
    
}
    });

};

我的 api 样例:

"contracts": 
[
    {
      "contractID": "Test1",
      "details": [
        {
          "timesheetDetailsId": 111111,
          "timesheetId": 0,
          "timesheetDate": "2021-11-01T00:00:00",
          "timesheetDayNumber": 1,
          "contractCode": "Test1",
          "activityCode": "GRA",
          "hours": 7.5,
          "otFlag": false,
          "notes": "Testing",
          "approved": false,
          "overUnder": 0.0,
          "employeeCode": "N-0510"
        },
        {
          "timesheetDetailsId": 111113,
          "timesheetId": 0,
          "timesheetDate": "2021-11-03T00:00:00",
          "timesheetDayNumber": 3,
          "contractCode": "Test1",
          "activityCode": "GRA",
          "hours": 7.5,
          "otFlag": false,
          "notes": "Testing",
          "approved": false,
          "overUnder": 0.0,
          "employeeCode": "N-0510"
        }
      ]
       "contractID": "Test2",
       "details": [
        {
          "timesheetDetailsId": 111112,
          "timesheetId": 0,
          "timesheetDate": "2021-11-02T00:00:00",
          "timesheetDayNumber": 2,
          "contractCode": "Test2",
          "activityCode": "GRA",
          "hours": 7.5,
          "otFlag": false,
          "notes": "Testing",
          "approved": false,
          "overUnder": 0.0,
          "employeeCode": "N-0510"
        }
      ]
]

让我们稍微整理一下。

您可以一次完成此操作,您可以根据数组中元素的数量知道一组中有多少项。您还可以将每个“组”包装在 tbody 元素中。

接下来我们将使用文档片段,这样我们只更新 DOM 一次,这将减少重绘,这在项目很多时很重要。

然后我们将使用事件委托来处理动态添加元素的点击事件。我还使用了数据属性而不是隐藏单元格。

//Making timesheet <td>
/*  Mocked some datat to emualte call
fetch(timehsheetUrl + date)
    .then(response => response.json())
    .then(data => makeTable(data.contracts))
    .catch(error => console.log(`Oh no! ${error}`))
*/

const makeTable = (contracts) => {
  const body = document.getElementById('tsdata');
  //Create a fragment to work with
  var fragment = new DocumentFragment();
  contracts.contracts.forEach((person, index) => {
    /*Create a group header row for each "person"*/
    let groupRow = document.createElement("tr");
    groupRow.classList.add("group_row");
    groupRow.innerHTML = `<td colspan="8">${person.contractID} : Found ${person.details.length}</td>`
    /*Create a tbody to hold each group*/
    let groupBody = document.createElement("tbody");
    groupBody.classList.add("group_body");
    /*Append to fragment*/    
    fragment.appendChild(groupRow);
    fragment.appendChild(groupBody);
    
    person.details.forEach((entry) => {
     
      const dateString = entry.timesheetDate;
      const dateObject = new Date(dateString);
      const dd = dateObject.getDate();

      const mm = dateObject.toLocaleString('en-us', {
        month: 'short'
      });
      const yyyy = dateObject.getFullYear();
      const chartDate = `${dd}-${mm}-${yyyy}`
      const htmlTemplate = `
    <tr class = "testDeleteTimesheet" data-timesheetdetailsid="${entry.timesheetDetailsId}">
      <td>${entry.contractCode}</td>    
      <td>${entry.activityCode}</td>
      <td>${entry.otFlag}</td>
      <td>${entry.notes}</td>
<td>${chartDate}</td>
<td class = "totalHours">${entry.hours}</td>
<td><button id=\"edit-" + counter + "\" class=\"btn editRow btnStyle btn-primary btn-sm\"><span class=\ "bi bi-pencil\"></span></button> 
<button id=\"delete-" + counter + "\" class=\"btn delRow btnStyle btn-danger btn-sm\"><span class=\"bi bi-eraser\"></span></button></td>
    </tr>
    `;
      /*Update the group  body*/
      groupBody.innerHTML += htmlTemplate;
    });
  });
  //Append the fragment to the table
  body.appendChild(fragment);
};

/*Event listener to toggle expantion, bound to table so we can use dynamically added element*/
document.getElementById("tsdata").addEventListener("click", function(event){
  /*Check the target of the event*/
  if(event.target.matches(".group_row>td")){
    //Toggle the class on the row and let CSS do the rest
    event.target.closest(".group_row").classList.toggle("expanded");
  }
});

let data = {
  "contracts": [{
    "contractID": "Test1",
    "details": [{
        "timesheetDetailsId": 111111,
        "timesheetId": 0,
        "timesheetDate": "2021-11-01T00:00:00",
        "timesheetDayNumber": 1,
        "contractCode": "Test1",
        "activityCode": "GRA",
        "hours": 7.5,
        "otFlag": false,
        "notes": "Testing",
        "approved": false,
        "overUnder": 0.0,
        "employeeCode": "N-0510"
      },
      {
        "timesheetDetailsId": 111113,
        "timesheetId": 0,
        "timesheetDate": "2021-11-03T00:00:00",
        "timesheetDayNumber": 3,
        "contractCode": "Test1",
        "activityCode": "GRA",
        "hours": 7.5,
        "otFlag": false,
        "notes": "Testing",
        "approved": false,
        "overUnder": 0.0,
        "employeeCode": "N-0510"
      }
    ]
  }, {
    "contractID": "Test2",
    "details": [{
      "timesheetDetailsId": 111112,
      "timesheetId": 0,
      "timesheetDate": "2021-11-02T00:00:00",
      "timesheetDayNumber": 2,
      "contractCode": "Test2",
      "activityCode": "GRA",
      "hours": 7.5,
      "otFlag": false,
      "notes": "Testing",
      "approved": false,
      "overUnder": 0.0,
      "employeeCode": "N-0510"
    }]
  }]
};

makeTable(data);
table{width:90%;}

/*Hide TBody By Default*/
.group_row:not(.expanded) + tbody {height:0; display:none;}
/*Set the cell to relative to position +/-*/
.group_row > td {position:relative;}
/*Expand Collapes indicator*/
.group_row > td::after {
  position:absolute;
  right: 5px;
  content: "+";
}

/*Change indicator*/
.group_row.expanded > td::after {content:"-";}
<table id="tsdata">
  <thead>
    <tr>
      <th>Contract Code</th>
      <th>Activity Code</th>
      <th>Overtime</th>
      <th>Notes</th>
      <th>Date</th>
      <th>Total Hours</th>
      <th> </th>
      <th> </th>
    </tr>
  </thead>
</table>