How to make HTML table expand on click?

前端 未结 6 1863
渐次进展
渐次进展 2020-12-06 05:05

I am rendering HTML table with the help of JavaScript. I have made the table successfully, but now I have one requirement to show some new data in a row like on click expand

6条回答
  •  余生分开走
    2020-12-06 05:55

    Nearly a pure JavaScript solution example breaking up the parts for headers a bit into functions, functions for the detail rows where it looks up the header to put amounts under - I added an item to demonstrate that.

    If you run this, you can expand the top notes to see detail notes of things I did. (click the big blue button)

    /*jshint esversion: 6 */
    
    var rawdata = [{
        "outlet": "JAYANAGAR",
        "brandname": "Bakery FG",
        "itemname": "Khara Boondhi-L",
        "transactionType": "TransferIn",
        "netamount": 980
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Bakery FG",
        "itemname": "Samosa-L",
        "transactionType": "TransferIn",
        "netamount": 130
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Bakery FG",
        "itemname": "Corn Flakes Masala-L",
        "transactionType": "TransferIn",
        "netamount": 500
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Pastry & Cake FG",
        "itemname": "Plum Cake 250gm",
        "transactionType": "TransferIn",
        "netamount": 110
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Pastry & Cake FG",
        "itemname": "Butterscotch Cake",
        "transactionType": "TransferIn",
        "netamount": 720
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Pastry & Cake FG",
        "itemname": "Chocolate chips cake",
        "transactionType": "TransferIn",
        "netamount": 40000
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Pastry & Cake FG",
        "itemname": "Mango Delight Cake",
        "transactionType": "TransferIn",
        "netamount": 14000
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Pastry & Cake FG",
        "itemname": "Almond Honey Chocolate Cake",
        "transactionType": "TransferIn",
        "netamount": 500
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Pastry & Cake FG",
        "itemname": "Peach Cake",
        "transactionType": "TransferIn",
        "netamount": 5500
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Pastry & Cake FG",
        "itemname": "Black Forest Cake",
        "transactionType": "TransferIn",
        "netamount": 1000
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "Chocolate Crazy Boom",
        "transactionType": "TransferIn",
        "netamount": 2360
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "Hot Chocolate Fudge",
        "transactionType": "TransferIn",
        "netamount": 2340
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "Chocolate Sugar Free Ice-Cream",
        "transactionType": "TransferIn",
        "netamount": 1000
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "Kesar Badam Falooda",
        "transactionType": "TransferIn",
        "netamount": 4430
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "Strawberry Ice-cream",
        "transactionType": "TransferIn",
        "netamount": 1231
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "TOP- Chocochips",
        "transactionType": "TransferIn",
        "netamount": 2200
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "Cheese Cake Ice-Cream",
        "transactionType": "TransferIn",
        "netamount": 500
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "Sundae Large",
        "transactionType": "TransferIn",
        "netamount": 2350
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "Mango Ice-cream",
        "transactionType": "TransferIn",
        "netamount": 8000
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "TOP- Shooting Star",
        "transactionType": "TransferIn",
        "netamount": 2360
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "Ice Blue Sundae",
        "transactionType": "TransferIn",
        "netamount": 2340
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "Creamy Litchi Boom",
        "transactionType": "TransferIn",
        "netamount": 2200
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "Cookies Ice-cream",
        "transactionType": "TransferIn",
        "netamount": 7000
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "TOP- Wafer",
        "transactionType": "TransferIn",
        "netamount": 88000
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "Litchi cherry Sundae",
        "transactionType": "TransferIn",
        "netamount": 2440
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "Peach Malaba",
        "transactionType": "TransferIn",
        "netamount": 2230
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "Ice Cream FG",
        "itemname": "Cherry Mania Ice-Cream",
        "transactionType": "TransferIn",
        "netamount": 2700
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "North Indian FG",
        "itemname": "Fruit Mixture",
        "transactionType": "TransferIn",
        "netamount": 324
      },
      {
        "outlet": "MALLESHWARAM",
        "brandname": "My Pie",
        "itemname": "Cherry Pie",
        "transactionType": "TransferIn",
        "netamount": 324
      },
      {
        "outlet": "JAYANAGAR",
        "brandname": "NA",
        "itemname": "NA",
        "transactionType": "Sales",
        "netamount": 476426
      },
      {
        "outlet": "KOLAR",
        "brandname": "NA",
        "itemname": "NA",
        "transactionType": "Sales",
        "netamount": 115313
      },
      {
        "outlet": "MALLESHWARAM",
        "brandname": "NA",
        "itemname": "NA",
        "transactionType": "Sales",
        "netamount": 92141
      }
    ];
    
    // basic functions, work even in old browsers like ie6
    var myApp = myApp || {};
    myApp.funcs = {
      indexOf: function(myArray, searchTerm, property) {
        for (var i = 0; i < myArray.length; i++) {
          if (myArray[i][property] === searchTerm) return i;
        }
        return -1;
      },
      indexAllOf: function(myArray, searchTerm, property) {
        var ai = [];
        for (var i = 0; i < myArray.length; i++) {
          if (myArray[i][property] === searchTerm) ai.push(i);
        }
        return ai;
      },
      lookup: function(myArray, searchTerm, property, firstOnly) {
        var found = [];
        var i = myArray.length;
        while (i--) {
          if (myArray[i][property] === searchTerm) {
            found.push(myArray[i]);
            if (firstOnly) break; //if only the first 
          }
        }
        return found;
      },
      exclude: function(myArray, searchTerm, property, firstOnly = false) {
        var found = [];
        var i = myArray.length;
        while (i--) {
          if (myArray[i][property] !== searchTerm) {
            found.push(myArray[i]);
            if (firstOnly) break; //if only the first 
          }
        }
        return found;
      },
      lookupAll: function(myArray, searchTerm, property) {
        return this.lookup(myArray, searchTerm, property, false);
      },
      arrSum: function(arr, selectorProp, selectorValue, numProp) {
        // get the summary (total) of any object array, assumes number
        function isSumMatch(item, index, arr) {
          return item[1][selectorProp] == selectorValue;
        }
        const arrSum = Object.entries(arr)
          .filter(isSumMatch)
          .map(item => item[1][numProp])
          .reduce((partial_sum, a) => partial_sum + a, 0);
        return arrSum;
      }
    };
    myApp.data = myApp.data || {
      items: rawdata
    };
    // could also do:
    //myApp.data = myApp.data || {};
    //myApp.data.items = myApp.data.items || rawdata;
    
    // add a function, could be in above also
    myApp.funcs.formatData = function(data) {
      let brandnames = [];
      let itemnames = [];
      let outlets = [];
      data.forEach(element => {
        //taking brandname which do not have bradname===NA
        if (brandnames.indexOf(element.brandname) == -1 && (element.brandname) !== "NA") {
          brandnames.push(element.brandname);
        }
        //taking itemname which do not have bradname===NA
        if (itemnames.indexOf(element.itemname) == -1 && (element.itemname) !== "NA") {
          itemnames.push(element.itemname);
        }
        if (outlets.indexOf(element.outlet) == -1) {
          outlets.push(element.outlet);
        }
      });
      return {
        //data: data,
        brandnames: brandnames,
        itemnames: itemnames,
        outlets: outlets,
      };
    };
    
    let renderHeader = function(data, targetTable) {
      let headId = targetTable.id + "-theadid";
      let thead = document.createElement("thead");
      thead.setAttribute("id", headId);
      let headerRow = document.createElement("tr");
      let headerInst = 0;
      let rowClass = headId + "-" + headerInst;
      headerRow.setAttribute("id", headId);
      headerRow.classList.add(rowClass);
      let th = document.createElement("th");
      // first header row
      th = document.createElement("th");
      th.innerHTML = "Brand Name";
      th.classList.add("text-center");
      headerRow.appendChild(th);
    
      th = document.createElement("th");
      th.colSpan = 2;
      th.innerHTML = "Total";
      th.classList.add("text-center");
      headerRow.appendChild(th);
      // first header row - outlets names
      data.formatedData.outlets.forEach(outlet => {
        th = document.createElement("th");
        th.colSpan = 2;
        th.setAttribute("data-outlet", outlet);
        th.innerHTML = outlet; // populating outlet 
        th.classList.add("text-center");
        headerRow.appendChild(th);
      });
      thead.appendChild(headerRow);
    
      /* entery header row */
      headerRow = document.createElement("tr");
      th = document.createElement("th");
      th.innerHTML = "";
      headerRow.appendChild(th);
      let i = 0;
      // entery header row
      for (i; i < data.formatedData.outlets.length + 1; i++) {
        th = document.createElement("th");
        th.innerHTML = "Sales";
        th.classList.add("text-center");
        headerRow.appendChild(th);
    
        th = document.createElement("th");
        th.innerHTML = "Grn Entery";
        th.classList.add("text-center");
        headerRow.appendChild(th);
      }
      thead.appendChild(headerRow);
      let oh = targetTable.getElementsByTagName('thead')[0];
      oh.parentNode.replaceChild(thead, oh);
      myApp.data.origHead = targetTable.getElementsByTagName('thead')[0];
      return headId;
    };
    
    let renderGrandTotal = function(data, targetTable, origHead) {
      let headerRow = document.createElement("tr");
      let th = document.createElement("th");
      th.innerHTML = "Total";
      th.classList.add("text-center");
      headerRow.appendChild(th);
    
      let el1 = 0;
      data.formatedData.outlets.forEach(element => {
        th = document.createElement("th");
        th.innerHTML = data.outletWiseTotal[element].toLocaleString('en-IN');
        th.classList.add("text-right");
        headerRow.appendChild(th);
        if (element.outlet == element) {
          el1 = element.netAmount;
        }
        th = document.createElement("th");
        th.innerHTML = data.outletWiseNetamount[element].toLocaleString('en-IN') || 0;
        th.classList.add("text-right");
        headerRow.appendChild(th);
      });
      th = document.createElement("th");
      th.innerHTML = data.grandNetAmount.toLocaleString('en-IN');
      th.classList.add("text-right");
      headerRow.insertBefore(th, headerRow.children[1]);
    
      th = document.createElement("th");
      th.innerHTML = data.grandTotal.toLocaleString('en-IN');
      th.classList.add("text-right");
      headerRow.insertBefore(th, headerRow.children[1]);
      origHead.appendChild(headerRow);
    };
    let getTotals = function(data) {
      data.outletWiseTotal = {};
      data.outletWiseNetamount = {};
      let na = "NA";
      let bn = "brandname";
      let num = 'netamount';
      let notJustOne = false;
      data.grandTotal = myApp.funcs.arrSum(myApp.funcs.exclude(data.items, na, bn, notJustOne), 'transactionType', 'TransferIn', num);
      data.grandNetAmount = myApp.funcs.arrSum(myApp.funcs.lookupAll(data.items, "NA", bn), 'transactionType', "Sales", num);
      data.formatedData.outlets.forEach(element => {
        data.outletWiseTotal[element] = 0;
        let myOutlet = myApp.funcs.lookupAll(data.items, element, "outlet");
        let notNA = myApp.funcs.exclude(myOutlet, na, bn);
        let justNA = myApp.funcs.lookupAll(myOutlet, na, bn);
        data.outletWiseTotal[element] = myApp.funcs.arrSum(notNA, 'outlet', element, num);
        data.outletWiseNetamount[element] = myApp.funcs.arrSum(justNA, 'transactionType', "Sales", num);
      });
      return data;
    };
    
    let findHeader = function(origHead, searchText) {
      let headers = origHead.getElementsByTagName("tr")[0]
        .getElementsByTagName("th");
    
      let found;
      let i = 0;
      for (i; i < headers.length; i++) {
        if (headers[i].dataset.outlet == searchText) {
          found = headers[i];
          break;
        }
      }
    
      return {
        head: headers,
        index: i,
        outletHeader: found
      };
    };
    
    function getHeadByCell(headerRow, cell) {
      var idx = $(cell).index(),
        th,
        th_colSpan = 0;
      let i = 0;
      for (i; i < headerRow.cells.length; i++) {
        th = headerRow.cells[i];
        th_colSpan += th.colSpan;
        let isThing = (th_colSpan >= (idx + cell.colSpan));
        if (th_colSpan >= (idx + cell.colSpan)) {
          break;
        }
      }
      return th;
    }
    
    let renderBrandDetailRow = function(rowdata, tblBody, brandname, brandClass) {
      // render stuff like Bakery FG
      let r = 0;
      for (r; r < rowdata.length; r++) {
        let row = document.createElement("tr");
        row.classList.add("collapse", brandClass);
        let td = document.createElement("td");
        td.classList.add("text-center");
        td.innerHTML = brandname;
        row.appendChild(td);
        // item name
        td = document.createElement("td");
        td.classList.add("text-center");
        td.colSpan = 2;
        td.innerHTML = rowdata[r]["itemname"];
        row.appendChild(td);
        // punch in empty column data first
        let groupOutletsCount = myApp.data.formatedData.outlets.length;
        for (let c = 0; c < (groupOutletsCount * 2); c++) {
          td = document.createElement("td");
          td.classList.add("text-right");
          row.appendChild(td);
        }
        let origHead = myApp.data.origHead;
        let found = findHeader(origHead, rowdata[r].outlet);
        let testRow = origHead.getElementsByTagName("tr")[1];
        let fr = 0;
        for (fr; fr < testRow.getElementsByTagName("th").length; fr++) {
          let mycell = testRow.getElementsByTagName("th")[fr];
          let ath = getHeadByCell(origHead.getElementsByTagName("tr")[0], mycell);
          if (ath == found.outletHeader) break;
        }
        // now we have the header that matches, put the data in the right place
        row.getElementsByTagName("td")[fr].innerHTML = rowdata[r]["netamount"].toLocaleString('en-IN');
        tblBody.appendChild(row);
      }
    };
    let renderTable = function(data) {
      let tbl = document.getElementById("ConsumptionTable");
      tbl.classList.add("table", "table-striped", "table-bordered", "table-hover");
      let headId = renderHeader(data, tbl);
      let origHead = document.getElementById(headId);
      let tbody = document.createElement("tbody");
    
      let headerInst = 0;
      let rowClass = headId + "-" + headerInst;
      renderGrandTotal(data, tbl, origHead);
      let collapseClass = 0;
      data.formatedData.brandnames.forEach(element => {
        let brandSum = myApp.funcs.arrSum(data.items, 'brandname', element, "netamount");
        let row = document.createElement("tr");
        let td = document.createElement("td");
        let brandClass = "multi-collapse-" + collapseClass;
        td.innerHTML = '  ' + element;
        row.appendChild(td);
        data.formatedData.outlets.forEach(outlet => {
          let outletSum = myApp.funcs.arrSum(myApp.data.items, 'outlet', outlet, 'netamount');
          data.olWiseSalesPercentage = (brandSum / outletSum) * 100 || 0;
          td = document.createElement("td");
          td.innerHTML = brandSum.toLocaleString('en-IN');
          td.classList.add("text-right");
          row.appendChild(td);
          td = document.createElement("td");
          td.innerHTML = data.olWiseSalesPercentage.toFixed(2) + "%";
          td.classList.add("text-right");
          row.appendChild(td);
        });
        data.totalSalesPercentage = (brandSum / data.grandTotal) * 100;
        const totalSalesPercentageFix = data.totalSalesPercentage.toFixed(2) + "%";
        td = document.createElement("td");
        td.innerHTML = totalSalesPercentageFix;
        td.classList.add("text-right");
        row.insertBefore(td, row.children[1]);
    
        td = document.createElement("td");
        td.innerHTML = brandSum.toLocaleString('en-IN');
        td.classList.add("text-right");
        row.insertBefore(td, row.children[1]);
        tbody.appendChild(row);
        let brandData = myApp.funcs.lookupAll(myApp.data.items, element, 'brandname');
        renderBrandDetailRow(brandData, tbody, element, brandClass);
        collapseClass++;
      });
      tbl.appendChild(tbody);
    };
    
    $('#things-i-did').find('.list-group').toggleClass('hidden', true);
    myApp.data.formatedData = myApp.funcs.formatData(myApp.data.items);
    getTotals(myApp.data);
    renderTable(myApp.data);
    
    let cttbl = document.getElementById('ConsumptionTable');
    cttbl.addEventListener('click', function(event) {
      let myExp = "expand-child-group";// detail row class
      if (event.target.classList.contains(myExp)) {
        let myAttr = event.target.dataset.target;
        //console.log(myAttr);
      }
    }, false);
    .expand-child-group {
      color: green;
      cursor: pointer;
    }
    
    .identify-me {
      background-color: lime;
    }
    
    
    
    
    
    
    
    Things I did:
    • Added this silly toggle list
    • Added a lot of missing semi-colons
    • Remove obvious comments "do calculation"
    • Consolidate the: td.classlist.add("my-class"); td.classlist.add("my-other-class"); to an array/list: td.classlist.add("my-class","my-other-class");
    • Several "undefined" variables used, removed or defined with let mything =
    • Clarify several variables used
    • Remove several "unused" variables
    • added base to parseInt functions
    • Used Event listener for click cttbl.addEventListener('click', function(event) {
    • change td = something to th = something variable to clarify intent
    • Removed id="test", duplicate ID's are invalid
    • Created an object var myApp = myApp || {}; to hold all the stuff (function, calculation data, globals) and avoid global variables
    • functions for each detail row under proper header

提交回复
热议问题