I'll keep this short and sweet. I have a table that prints everything I need it to just fine. What I am trying to do is group the data rows under Program 1 together so instead of Program 1 printing, inserting the data, then printing again followed by another set of data, I want it to look like the "Expected Results" table. There are going to be way more than 2 per program, just used that as an example. I have been stuck on this for a while and just cannot seem to figure it out. I also would like to be able to collapse and expand these rows that way not everything is showing
Actual Result:

**Expected Result**
+------------+----------------------+-----------+------------+--------------+--------------+
| Program | To | Date | Approved | Notes | Deliverable |
+------------+----------------------+-----------+------------+--------------+--------------+
| Program 1 | example@example.com | 12/23/2018| Yes | Example Notes| MSR |
| | example@example.com | 03/30/2020| Yes | Example Notes| Meeting Mins |
+------------+----------------------+-----------+------------+--------------+--------------+
| Program 2 | example@example.com | 12/23/2018| Yes | Example Notes| MSR |
| | example@example.com | 12/03/2017| Yes | Example Notes| Meeting Mins |
+------------+----------------------+-----------+------------+--------------+--------------+
| Program 3 | example@example.com | 04/17/2020| Yes | Example Notes| MSR |
| | example@example.com | 03/30/2020| No | Example Notes| Meeting Mins |
+------------+----------------------+-----------+------------+--------------+--------------+
Here is my code:
<input type="text" id="myInput" onkeyup="searchTable()" placeholder="Search by Program Name" title="Enter Program Name">
<script src="/Scripts/jquery-3.3.1.min.js"></script>
<script>
var webAppUrl = _spPageContextInfo.webAbsoluteUrl;
function loadData(source, url) {
return fetch(url, { headers: { accept: "application/json; odata=verbose" } }) // make request
.then((r) => {
if (!r.ok) throw new Error("Failed: " + url); // Check for errors
return r.json(); // parse JSON
})
.then((data) => data.d.results) // unwrap to get results array
.then((results) => {
results.forEach((r) => (r.source = source)); // add source to each item
return results;
});
}
window.addEventListener("load", function () {
Promise.all([
loadData("XDeliverables", webAppUrl + "/_api/web/lists/getbytitle('XDeliverables')/items?$select=Program,To,Date,Approved,Notes,Deliverable"),
loadData("YDeliverables", webAppUrl + "/_api/web/lists/getbytitle('YDeliverables')/items?$select=Program,To,Date,Approved,Notes,Deliverable"),
loadData("ZDeliverables", webAppUrl + "/_api/web/lists/getbytitle('ZDeliverables')/items?$select=Program,To,Date,Approved,Notes,Deliverable"),
])
.then(([r1, r2, r3]) => {
const objItems = r1.concat(r2,r3);
console.log(objItems);
var tableContent =
'<table id="deliverablesTable" style="width:100%" border="1 px"><thead><tr><td><strong>Program</strong></td>' +
"<td><strong>To</strong></td>" +
"<td><strong>Date Submitted</strong></td>" +
"<td><strong>Approved</strong></td>" +
"<td><strong>Notes</strong></td>" +
"<td><strong>Deliverable</strong></td>" +
"</tr></thead><tbody>";
for (var i = 0; i < objItems.length; i++) {
tableContent += "<tr>";
tableContent += "<td>" + objItems[i].Program + "</td>";
tableContent += "</tr>";
tableContent += "<tr>";
tableContent += "<td> </td>";
tableContent += "<td>" + objItems[i].To + "</td>";
tableContent += "<td>" + objItems[i].Date + "</td>";
tableContent += "<td>" + objItems[i].Approved + "</td>";
tableContent += "<td>" + objItems[i].Notes + "</td>";
tableContent += "<td>" + objItems[i].Deliverable + "</td>";
tableContent += "</tr>";
}
$("#deliverables").append(tableContent);
})
.catch((err) => {
alert("Error: " + err);
console.error(err);
});
});
Here is the Object Array returned in objItems
{"Notes": "Example Notes", "Approved": "Yes", "Program": "Prorgam 1", "Date": "12/23/2018", "To": "example@example.com", "source": "XDeliverables", "Deliverable": "Monthly Status Report (MSR)"},
{"Notes": "Example Notes", "Approved": "Yes", "Program": "Program 1", "Date": "03/30/2020", "To": "example@example.com", "source": "XDeliverables", "Deliverable": "Meeting Minutes"},
{"Notes": "Example Notes", "Approved": "Yes", "Program": "Program 2", "Date": "12/23/2018", "To": "example@example.com", "source": "YDeliverables", "Deliverable": "Monthly Status Report (MSR)"},
{"Notes": "Example Notes", "Approved": "Yes", "Program": "Program 2", "Date": "12/3/2017", "To": "example@example.com", "source": "YDeliverables", "Deliverable": "Meeting Minutes"},
{"Notes": "Example Notes", "Approved": "No", "Program": "Program 3", "Date": "4/17/2020", "To": "example@example.com", "source": "ZDeliverables", "Deliverable": "Monthly Status Report (MSR)"},
{"Notes": "Example Notes", "Approved": "Yes", "Program": "Program 3", "Date": "12/23/2018", "To": "example@example.com", "source": "ZDeliverables", "Deliverable": "Meeting Minutes"},