6

I have a table in html. The table utilizes generic tr's and td's... and makes a dynamic table with six columns and a variable number of rows.

If a given table looks something like this:

|column 1|column 2|column 3|column 4|column 5|column 6|
-------------------------------------------------------
|  John  |  Green |apples  |February|  cow   |   23   |
-------------------------------------------------------
|  John  |  Red   |Oranges |February|  lion  |   18   |
-------------------------------------------------------
|  John  |  Blue  |apples  |February|  cow   |   45   |
-------------------------------------------------------
|  Mary  |  Blue  |oranges |  April |  cow   |   23   |
-------------------------------------------------------
|  Mary  |  Blue  |apples  |   May  |  dog   |   49   |
-------------------------------------------------------
|  Mary  |  green |apples  |  June  |  cat   |   67   |
-------------------------------------------------------
|  Mary  |  red   |apples  |  June  |  mouse |   32   |
-------------------------------------------------------

When I run the following javascript:

function MergeCommonRows(table, columnIndexToMerge) {
previous = null;
cellToExtend = null;
table.find("td:nth-child(" + columnIndexToMerge + ")").each(function() {
    jthis = $(this);
    content = jthis.text()
    if (previous == content && content !== "") {
        jthis.remove();
        if (cellToExtend.attr("rowspan") == undefined) {
            cellToExtend.attr("rowspan", 2);
        }
        else {
            currentrowspan = parseInt(cellToExtend.attr("rowspan"));
            cellToExtend.attr("rowspan", currentrowspan + 1);
        }
    }
    else {
        previous = content;
        cellToExtend = jthis;
    }
});
}

The following table is made:

|column 1|column 2|column 3|column 4|column 5|column 6|
-------------------------------------------------------
|        |  Green |apples  |        |  cow   |   23   |
         ------------------         -------------------
|  John  |  Red   |Oranges |February|  lion  |   18   |
         ------------------         -------------------
|        |        |apples  |        |        |   45   |
---------         ------------------         ----------
|        |  Blue  |oranges | April  |  cow   |   23   |
                  ------------------         ----------
|  Mary  |        |        |  May   |        |   49   |
          --------         ----------------------------
|        |  green | apples |  June  |  cat   |   67   |
          --------         ----------------------------
|        |  red   |        |  July  |  mouse |   32   |
-------------------------------------------------------

Now, the javascript works in the sense that I do need the rows to merge as seen in the above table. The first column merges nicely. And other areas in the columns do so as well. My only issues arises in areas like columns 2 and five. Now to reiterate, these tables will always be dynamically generated so I can't approach this in a static fashion. But... the merging for "blue" in column 2 and "cow" in column 5 goes past the associated values in column 1. Instead, I would want a table like this:

|column 1|column 2|column 3|column 4|column 5|column 6|
-------------------------------------------------------
|        |  Green |apples  |        |  cow   |   23   |
         ------------------         -------------------
|  John  |  Red   |Oranges |February|  lion  |   18   |
         ------------------         -------------------
|        |  Blue  |apples  |        |  cow   |   45   |
-------------------------------------------------------
|        |  Blue  |oranges | April  |  cow   |   23   |
                  ------------------         ----------
|  Mary  |        |        |  May   |        |   49   |
          --------         ----------------------------
|        |  green | apples |  June  |  cat   |   67   |
          --------         ----------------------------
|        |  red   |        |  July  |  mouse |   32   |
-------------------------------------------------------

In the above table, the merged cells for "blue" and "cow" separate because the cells preceding it end their rowspan there. This would occur in any case. If a cell in the second column happened to span multiple rows, then any subsequent columns could not have rowspans extending past it. Hopefully what I have described is clear enough. I am asking... how do I revise my javascript to achieve this effect?

newuser
  • 173
  • 1
  • 3
  • 13
  • Please let me know if any additional information is needed to adequately approach this question. This is my first attempt at writing a question haha. :D – newuser Feb 13 '15 at 05:14

2 Answers2

15

If only the first column is supposed to be a "divider" for the rest of the columns, then you could use an array to store first column rows "breaks".

Also, your function doesn't have the right to work. When element is removed inside loop, The index of each element above the removed one is shifted down immediately to fill the DOM index gap (so the element right after the removed one is skipped in next iteration). You could use "decremental" loop or simply hide td's.

function MergeCommonRows(table) {
    var firstColumnBrakes = [];
    // iterate through the columns instead of passing each column as function parameter:
    for(var i=1; i<=table.find('th').length; i++){
        var previous = null, cellToExtend = null, rowspan = 1;
        table.find("td:nth-child(" + i + ")").each(function(index, e){
            var jthis = $(this), content = jthis.text();
            // check if current row "break" exist in the array. If not, then extend rowspan:
            if (previous == content && content !== "" && $.inArray(index, firstColumnBrakes) === -1) {
                // hide the row instead of remove(), so the DOM index won't "move" inside loop.
                jthis.addClass('hidden');
                cellToExtend.attr("rowspan", (rowspan = rowspan+1));
            }else{
                // store row breaks only for the first column:
                if(i === 1) firstColumnBrakes.push(index);
                rowspan = 1;
                previous = content;
                cellToExtend = jthis;
            }
        });
    }
    // now remove hidden td's (or leave them hidden if you wish):
    $('td.hidden').remove();
}

$('.button').click(function(){
    MergeCommonRows($('#tbl'));
});
table {
    border-collapse: collapse;
}

th, td {
    border: 1px solid black;
    padding:5px;
    text-align: center;
}

.hidden{
    display:none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<table id=tbl>
    <thead>
        <tr>
            <th>column 1</th>
            <th>column 2</th>
            <th>column 3</th>
            <th>column 4</th>
            <th>column 5</th>
            <th>column 6</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>John</td>
            <td>Green</td>
            <td>apples</td>
            <td>February</td>
            <td>cow</td>
            <td>23</td>
        </tr>
        <tr>
            <td>John</td>
            <td>Red</td>
            <td>Oranges</td>
            <td>February</td>
            <td>lion</td>
            <td>18</td>
        </tr>
        <tr>
            <td>John</td>
            <td>Blue</td>
            <td>apples</td>
            <td>February</td>
            <td>cow</td>
            <td>45</td>
        </tr>
        <tr>
            <td>Mary</td>
            <td>Blue</td>
            <td>Oranges</td>
            <td>April</td>
            <td>cow</td>
            <td>23</td>
        </tr>
        <tr>
            <td>Mary</td>
            <td>Blue</td>
            <td>apples</td>
            <td>May</td>
            <td>cow</td>
            <td>49</td>
        </tr>
        <tr>
            <td>Mary</td>
            <td>green</td>
            <td>apples</td>
            <td>June</td>
            <td>cat</td>
            <td>67</td>
        </tr>
        <tr>
            <td>Mary</td>
            <td>red</td>
            <td>apples</td>
            <td>June</td>
            <td>mouse</td>
            <td>32</td>
        </tr>
    </tbody>
</table>

<p><button class=button>Merge Rows</button></p>
Artur Filipiak
  • 8,700
  • 4
  • 27
  • 56
  • This code looks extremely promising. I have a question though. At the beginning, you said "If only the first column is supposed to be a 'divider' for the rest of the columns, then you could use an array to store first column rows 'breaks'." What if, instead, each column acts as a divider for subsequent columns? So the first column is a divider for all of the columns to the right. However, say a cell in the first column spans 5 and the subsequent column spans 3 and the subsequent one 4. This third column would need to be "divided" at span 3 because the previous column's adjacent cell ends. – newuser Feb 14 '15 at 22:14
  • Does what I said make sense? Or should I explain better? Perhaps I was a bit convoluted. Let me know. But again thank you again for the answer. If the solution to the above question (which was my original intention) is not solvable,etc, I'll mark yours as the correct answer for atleast the way you interpreted it :D – newuser Feb 14 '15 at 22:16
  • I guess I got your point (each column `rowspan` make divider for the next column rows). I was thinking the same when I wrote the code, but dropped the idea as it would still leve a place for further optimisations - like merging columns that have the same values but not next to each other (lets say **John** has a *cow , lion* and *cow* again, so there's a place to make a *cow* with `rowspan=2` and a *lion* with `rowspan=1` :-) ). I will think about and I'll give you know. It will for sure require a 2D array and **at least** one more loop (through the array) – Artur Filipiak Feb 14 '15 at 22:32
  • Alrighty thanks. That would be greatly appreciated. I will also try to fiddle with your code and see if I get the desired effect, but given the way you wrote that, I'm fairly certain you're much better at this than I am. Regardless, this will be the answer! Thanks! – newuser Feb 14 '15 at 22:35
  • BTW. I saw your another question - [jQuery onlick table cell get table row content...](http://stackoverflow.com/questions/28498598/jquery-onlick-table-cell-get-table-row-content-regardless-of-rowspan) . You can get this to work if you **do not** remove hidden fields (like I've commented in the code above) – Artur Filipiak Feb 14 '15 at 22:43
  • That' s exactly what I thought. You essentially answered two questions with one here man. Haha thank you! – newuser Feb 14 '15 at 22:49
  • Should I remove that question? Or is there a way you could answer it? And I'll mark you as correct?...Beause you are. – newuser Feb 14 '15 at 22:50
  • No, you could remove it if you want to. I'm not answering for "reps farming". I just like challenges :-) I'll try to find something there to get you closer with the "Inherited dividers". I'll let you know. – Artur Filipiak Feb 14 '15 at 22:57
  • You sir, are a highly respectable man :D. Well it's deleted. Thanks again! And yes please let me know! I'm currently fiddling with it to see if I can do it :D – newuser Feb 15 '15 at 00:34
  • @newuser , that was pretty easy :-) All what should be done is to replace : `if(i === 1)` with `if($.inArray(index, firstColumnBrakes) === -1)` (to store "Inherited dividers" for each previous column). I made two different "merge modes" and added click events with output (also for merged cells) - [**Demo**](http://jsfiddle.net/gfdL4ay8/) – Artur Filipiak Feb 15 '15 at 12:53
  • Thank you! This worked perfectly! AND you helped get the output for the row! This was magnificent. I really appreciate this. :D – newuser Feb 15 '15 at 14:30
  • Hey Phillip. I was trying to implement the newest version of the code you provided, and I stumbled a bit on this particular part. I have updated the jsfiddle you provided. It's here: http://jsfiddle.net/gfdL4ay8/19/ the layout of the table is more consistent with what mine will always look like. So when I merge based on all previous columns, i run into a bit of an error. In column 4, a bit down there are two cells "op you". After hitting merge it doesn't work. They don't merge although all cells before them are merged. – newuser Feb 15 '15 at 18:04
  • Is this issue because there are cells that are empty or...? – newuser Feb 15 '15 at 18:05
  • Your table is inconsistent. (`colspans` are messed up) F.ex.: you have 6 columns, but some of the rows contains 9 `` elements (because of colspans). Normalize your table content first, so that number of `td` elements cover number of `th` columns. I used `reset()` function in my example, right after the page is loaded. Take a look how your table look like when `reset()` is disabled on load: [**JSFiddle**](http://jsfiddle.net/gfdL4ay8/23/) :-) – Artur Filipiak Feb 15 '15 at 18:20
  • oh right silly me. But okay I did that. I think this has the correct number of elements right? http://jsfiddle.net/gfdL4ay8/24/ – newuser Feb 15 '15 at 18:37
  • If you run the merge it still doesn't merge that spot. Unless I missed some other areas? – newuser Feb 15 '15 at 18:37
  • Using `colspan` you're making "fake" `td` elements (that doesn't really exists. They're just spaces in the table). Like I wrote above - make sure that number of `td` elements match the number of `th` elements. You could add missing `td`'s to the row where the collspan is set and persistently hide them - [**JSFiddle**](http://jsfiddle.net/gfdL4ay8/25/) – Artur Filipiak Feb 15 '15 at 18:53
  • If I add three extra td's after every colspan one and make their classes = hidden then it should work right? Because you're correct. I tested it and removing the colspan and having all td's... it works. But if I needed the colspans there, how would that then work? Thanks for all the help you've provided. This is complex to me D: – newuser Feb 15 '15 at 18:57
  • 1
    I would use other class, as `hidden` is removed on `reset`. See http://jsfiddle.net/gfdL4ay8/25/ – Artur Filipiak Feb 15 '15 at 19:02
  • Hi Artur,I was wondering if you could help me out. The answer to this question has been working like a charm, except now I've run into a dilemma. It seems that someone else has already asked the question, so I was wondering if you could take a look at it. He even references to what you did here. http://stackoverflow.com/questions/29546348/merged-table-rows-consider-classes – newuser Apr 14 '15 at 18:27
  • @newuser , I've looked and posted a comment on that question. – Artur Filipiak Apr 16 '15 at 18:49
  • Hi artur. I was looking at this code, and I was wondering... is there a way to limit the number of rows that your function will act on? Like perhaps only merging the first 20 rows? – newuser Jul 22 '15 at 19:19
  • 1
    Sure. Just pass an indicator (eg.: *`limit`*) to the function and check whether `index > limit` inside loop. http://jsfiddle.net/gfdL4ay8/36/ – Artur Filipiak Jul 22 '15 at 19:40
2

How about a simple addition to watch the first column, like so:

function MergeCommonRows(table, columnIndexToMerge) {
  previous = null;
  cellToExtend = null;
  col1 = null;
  table.find("td:nth-child(" + columnIndexToMerge + ")").each(function() {
      jthis = $(this);
      content1 = jthis.parent('tr').children().first().text();
      if (col1 !== content1) {
          previous = null;
          col1 = content1;
      }
      content = jthis.text()
      if (previous == content && content !== "") {
          jthis.remove();
          if (cellToExtend.attr("rowspan") == undefined) {
              cellToExtend.attr("rowspan", 2);
          }
          else {
              currentrowspan = parseInt(cellToExtend.attr("rowspan"));
              cellToExtend.attr("rowspan", currentrowspan + 1);
          }
      }
      else {
          previous = content;
          cellToExtend = jthis;
      }
  });
  }
  • 1
    After looking at the comments on the above question, does this do that same? Does it only divide by the first column or by all prior columns? – newuser Feb 14 '15 at 22:36