How to generating table that will have 3 columns per row

Viewed 398

I use Vue 2.

I generate table from the collection:

  <table class="table table-striped table-bordered">
      <tbody>
        <tr v-for="(type, typeIndex) in getAllUpgradeTypes" :key="typeIndex">
          <td>
            <imageDesc
              style="margin-left: 10px"
              :title="type"
              :text="'Select an Option'"
              :zoneId="selectedZone.id"
            />
          </td>
        </tr>
      </tbody>
    </table>
    

Currently, the table created with multiple rows, each row has only one column and shows one item inside the column.

My question how can I generate a table which will contain 3 columns per row?

2 Answers

There at least a couple of different patterns you could use for this. One involves rearranging your data before iterating it in the template, another doesn't. I'll show you both.

In both cases, use a data property to hold the number of columns:

data: () => ({
  numCols: 3
})

Reorganize the data into a 2D array

Instead of one array with all your items, use a computed to reorganize the data into a multi-dimensional array of rows and columns:

computed: {
  arrangedData() {
    const arrangedData = [];
    this.getAllUpgradeTypes.forEach((item, index) => {
      if (index % this.numCols === 0) {
        arrangedData.push([])
      }
      arrangedData[arrangedData.length - 1].push(item);
    });
    return arrangedData;
  }
}

Iterate the data like this:

<table class="table table-striped table-bordered">
  <tr v-for="(row, rowIndex) in arrangedData" :key="rowIndex">
    <td v-for="(type, typeIndex) in row" :key="`${rowIndex}:${typeIndex}`">
      {{ type }}
    </td>
  </tr>
</table>

Here's a demo:

new Vue({
  el: "#app",
  data: () => ({
    getAllUpgradeTypes: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'],
    numCols: 3
  }),
  computed: {
    arrangedData() {
      const arrangedData = [];
      this.getAllUpgradeTypes.forEach((item, index) => {
        if (index % this.numCols === 0) {
          arrangedData.push([])
        }
        arrangedData[arrangedData.length - 1].push(item);
      });
      return arrangedData;
    }
  }
});
td { width: 40px }
<div id="app">
  <table class="table table-striped table-bordered">
    <tr v-for="(row, rowIndex) in arrangedData" :key="rowIndex">
      <td v-for="(type, typeIndex) in row" :key="`${rowIndex}:${typeIndex}`">
        {{ type }}
      </td>
    </tr>
  </table>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

-or-

Calculate the number of rows

The other way involves using a computed to calculate the number of rows. It's less code, but then type gets an ugly syntax in the template:

computed: {
  numRows() {
    return Math.ceil(this.getAllUpgradeTypes.length / this.numCols);
  }
}

Iterate the data like this:

<table class="table table-striped table-bordered">
  <tr v-for="(row, indexRow) in numRows">
    <td v-for="(col, indexCol) in numCols">
      {{ getAllUpgradeTypes[(indexRow * numCols) + indexCol] }}
    </td>
  </tr>
</table>

Here's a demo:

new Vue({
  el: "#app",
  data: () => ({
    getAllUpgradeTypes: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'],
    numCols: 3
  }),
  computed: {
    numRows() {
      return Math.ceil(this.getAllUpgradeTypes.length / this.numCols);
    }
  }
});
td { width: 40px; }
<div id="app">
  <table>
    <tr v-for="(row, indexRow) in numRows" :key="indexRow">
      <td v-for="(type, indexCol) in numCols" :key="`${rowIndex}:${typeIndex}`">
        {{ getAllUpgradeTypes[(indexRow * numCols) + indexCol] }}
      </td>
    </tr>
  </table>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

There are probably some variations on these options but I think the first is probably the best for the template clarity.

Here is my solution to a similar issue. I needed to generate a printable page and display information from a table in our database. The appearance of the page required me to have 3 fixed-size rows and 3 fixed-size columns per row.

I added styling to give it a grid-like appearance, but here is how I generated the table with page breaks after 3 rows:

This is where I have the table setup in the HTML (cshtml) file:

<table id="employee-table" class="m-auto">

</table>

Here is my JavaScript function which is located in an external JS file (don't forget to reference the file in your HTML if that's how you're setting it up)

function GeneratePrintablePDF(employeeData) {
    document.getElementById('loader').classList.add('hidden'); //this is just the "loading" spinner for when the page is loading
    dataContainer = document.getElementById('employee-table');

    numberOfRows = employeeData.length / 3;

    if (employeeData % 3 != 0) {
        numberOfRows = Math.trunc(numberOfRows);
        numberOfRows += 1;
    } 

    var emp = 0;

    do
    {
        for (var row = 1; row <= numberOfRows; row++) {
            var tableRowDiv = document.createElement('div');
            dataContainer.appendChild(tableRowDiv);

            var tableRow = document.createElement('tr');
            tableRowDiv.appendChild(tableRow);

            for (var col = 1; col < 4; col++)
            {
                if (emp < employeeData.length) {
                    var tableCol = document.createElement('td');
                    tableCol.className = 'pdf-employee-box';
                    tableRow.appendChild(tableCol);

                    var empName = document.createElement('div');
                    empName.className = 'fw-bold';
                    empName.innerHTML = `${employeeData[emp].firstName} ${employeeData[emp].lastName}`;
                    tableCol.appendChild(empName);

                    var empPhoto = document.createElement('img');

                    var employeePhotoUrl = employeeData[emp].azurePhotoURL;
                    if (employeePhotoUrl == null) {
                        empPhoto.src = '/Images/lg_unavailable.jpg';
                    } else {
                        empPhoto.src = employeePhotoUrl;
                    }

                    var empPhotoOnError = document.createAttribute('onerror');
                    empPhotoOnError.value = "this.onerror=null;this.src='/Images/lg_unavailable.jpg';";
                    empPhoto.setAttributeNode(empPhotoOnError);

                    empPhoto.alt = 'Employee Photo';
                    empPhoto.className = 'employee-photo-pdf pdf-image';
                    tableCol.appendChild(empPhoto);

                    var empTitle = document.createElement('div');
                    empTitle.innerHTML = employeeData[emp].title != null ? `Title: ${employeeData[emp].title}` : 'Title: Unknown';
                    empTitle.className = 'pdf-details';
                    tableCol.appendChild(empTitle);

                    var empDept = document.createElement('div');
                    empDept.innerHTML = employeeData[emp].department != null ? `Department: ${employeeData[emp].department}` : 'Department: Unknown';
                    empDept.className = 'pdf-details';
                    tableCol.appendChild(empDept);

                    var empEmail = document.createElement('div');
                    empEmail.innerHTML = (employeeData[emp].emailAddress != null && !employeeData[emp].emailAddress.toLowerCase().includes('no ')) ? `Email: ${employeeData[emp].emailAddress}` : 'Email: Unknown/None';
                    empEmail.className = 'pdf-details';
                    tableCol.appendChild(empEmail);

                    var empStartDate = document.createElement('div');
                    empStartDate.innerHTML = employeeData[emp].startDate != null ? `Start Date: ${employeeData[emp].startDate}` : 'Start Date: Unknown';
                    empStartDate.className = 'pdf-details';
                    tableCol.appendChild(empStartDate);

                    var empLocation = document.createElement('div');
                    empLocation.innerHTML = employeeData[emp].location != null ? `Location: ${employeeData[emp].location}` : 'Location: Unknown';
                    empLocation.className = 'pdf-details';
                    tableCol.appendChild(empLocation);

                    if (col == 2) tableCol.className = tableCol.className + ' pdf-grid-lines';

                    emp++;
                }  
            }

            if (emp % 9 == 0) {
                tableRowDiv.className += 'page-break-after';
            }
        }
    } while (emp < employeeData.length)
    
    
}

I decided to calculate the number of rows I need based on how many employee records we have. So, if we have 436 employees, then I'm determining that I need 146 rows. So I'm programming it to generate the precise number of rows I need, and only put 3 items (columns) in each row. The 146th row in this case only has 1 record.

As you can see, I have the code in a loop only running until it reaches the last employee, so that it doesn't throw an index out of range exception.

This particular code is where I am telling the table to break to the next page, so that employee records aren't running over onto another page and splitting up the information.

if (emp % 9 == 0) {
   tableRowDiv.className += 'page-break-after';
}

Here is the CSS that I'm referencing in an external CSS file:

@media print {
    .page-break-after {
        break-after: page;
    }
}

I understand that I am by no means the ultimate programming kingpin, so there could be better or easier ways of doing this. However, this is how I made it work with my environment and it does exactly what I need it to do without using some framework or library.

I found in my research that many PDF generator frameworks, libraries, etc. now require licensing. So if generating a printable document is your goal, then this could work for you too. Anyways, good luck and I hope I have helped someone.

Related