2

I'm trying to develop a schedule where I can click any "time" row and drag down to span that row as far as the row that I 'mouseup'. For example, if I click down at 7:30 am and drag down to 9:00, the table data element will rowspan the appropriate number of rows so that table data becomes one block from 7:30 to 9:00 in that column (ie. Monday). This is the function of the "Add Appointment" button.

My plan was to store the index value for the tr when I click down, and then subtract that value from the index of the tr that I hover to (while mouse is down). For example, if I click down at row 1, drag to row 3 then mouse up, then the td that was first clicked should have its rowspan attribute set to 2.

Currently my function will span a number of rows solely depending on how far down I hover. So it is clearly not subtracting the initial index from clicking down. I'm not sure why. Any tips?

https://jsfiddle.net/Adam_M/fypw7jka/

Here is my HTML for the schedule and buttons:

   <div class="row" id="control-panel">
    <button onclick="addAppt()" id="add-appt" title="Add Appointment">+</button>
    <p>= Add Appointment</p>
    <button onclick="delAppt()" id="del-appt" title="Delete Appointment">-</button>
    <p>= Delete Appointment</p>
</div>

<table>
      <thead>
        <tr class="days-of-the-week">
          <th scope="col" class="time-col"></th>
          <th scope="col">Sunday</th>
          <th scope="col">Monday</th>
          <th scope="col">Tuesday</th>
          <th scope="col">Wednesday</th>
          <th scope="col">Thursday</th>
          <th scope="col">Friday</th>
          <th scope="col">Saturday</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th scope="row">7:30 AM</th>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
        </tr>
        <tr>
          <th scope="row">8:00 AM</th>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
        </tr>
        <tr>
          <th scope="row">8:30 AM</th>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
        </tr>
        <tr>
          <th scope="row">9:00 AM</th>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
          <td rowspan=""><textarea cols="20" rows="5" class="appt-text"></textarea></td>
        </tr>
      </tbody>
</table>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<script type="text/javascript" src="scheduler.js"></script>

Javascript for the relevant function:

function addAppt() {
$('td').css('cursor', 'cell');
$('textarea').css('cursor', 'cell');
$('td').mousedown(function(){
    var initIndex = $(this).parent().index(); 
    $('td').hover(function(){
        var spanCount = $(this).parent().index(); 
        var span = spanCount - initIndex;
        $(this).attr( 'rowspan', span);
        $(this).addClass('highlight');
      });
});
$('td').mouseup(function(){
  $('td').off('mouseenter mouseleave');
});
}
K Scandrett
  • 16,390
  • 4
  • 40
  • 65
Adam M.
  • 61
  • 11
  • Your fiddle doesn't work - it is missing the script `scheduler.js`. – K Scandrett Mar 03 '17 at 02:55
  • @KScandrett It works when I click on it... I just copied and pasted the script from scheduler.js into the jsfiddle's javascript section. It should be there. – Adam M. Mar 03 '17 at 19:04
  • Here is an updated fiddle of my attempt at what I described above. https://jsfiddle.net/Adam_M/fypw7jka/5/ The rows are being spanned based on the index of the row that I click. So the variable initIndex is not being subtracted as I intended it to be. I also have an unexpected function of columns being added to the right side of the table when I drag. I have no idea that is happening. – Adam M. Mar 03 '17 at 23:24
  • The columns added on right are because when you create a `rowspan` you need to remove the existing `td` from the rows spanned – K Scandrett Mar 04 '17 at 00:47
  • @KScandrett I added a removeCellSpan() function to your code with right click. The whole edit is within (local to) to your function that you wrote. So far I have learned a lot from your code and it is going well. But I am trying to figure out what else is missing that makes the newly appended elements not recognized by the rest of the table. I am trying to get everything back to the original state. I noticed the instances of row after $thisCell cannot be highlighted again after I remove the span and add elements in. It seems that the matrix becomes one column short for those rows. Any ideas? – Adam M. Mar 06 '17 at 21:47
  • @KScandrett if you prefer I can ask a new question for this ^ – Adam M. Mar 06 '17 at 21:49
  • Yes, this needs to be a new question. Post the link and I'll take a look. Also, best to rollback your edits on this one – K Scandrett Mar 06 '17 at 21:56
  • No problem. I'll post a new question. What do you mean by rollback? – Adam M. Mar 06 '17 at 22:02
  • If you edit your question again you'll see there is a rollback link on revision 2. If you click that it will undo the changes made today – K Scandrett Mar 06 '17 at 22:05
  • I rolled back the edits to before I asked a different question. Good suggestion. I posted the new question here: http://stackoverflow.com/questions/42637153/removing-rowspan-from-a-table-in-jquery – Adam M. Mar 06 '17 at 23:00

2 Answers2

2

It was more difficult that I expected. But anyway...

$(function() {

  var $table = $("#myTable tbody");
  var $rows = $table.children("tr");
  var $cells = $table.find("td").not(".rowHdr");

  var numCells = $cells.length;
  var numRows = $rows.length;
  var numCols = numCells / numRows; // skip row headings

  // track which columns have rowspans by setting to 1
  var matrix = new Array(numRows).fill(new Array(numCols).fill(0));

  var matrix = new Array(numRows);
  // init 2d matrix
  for (var i = 0; i < 10; i++) {
    matrix[i] = new Array(numCols);
  }
  //matrix[1][3] = 1; // test blocking cell
  var startCol, startRow, endRow, lastValidCell;

  var cellDown, cellOver, cellUp;
  var mouseDown = false;

  // used mouse event code from http://stackoverflow.com/a/19164149/1544886
  $cells.on('mousedown touchstart', function(event) {
    var cellPos;

    cellDown = this;

    event.preventDefault();
    mouseDown = true;

    cellPos = findCell(cellDown);

    if (cellPos) {
      startCol = cellPos.col;
      startRow = endRow = cellPos.row;
      highlightCells();
    } else {
      clearHighlights();
    }
  });

  $cells.on('mousemove touchmove', function(event) {
    event.preventDefault();

    if (mouseDown && cellOver != this) {
      var cellPos;

      cellOver = this;
      cellPos = findCell(cellOver);

      if (cellPos) {
        // limit to starting column only
        if (cellPos.col === startCol) {
          endRow = cellPos.row;
          highlightCells();
        }
      }
    }
  });

  $cells.on('mouseup touchend', function(event) {
    var cellPos;

    event.preventDefault();

    cellUp = this;
    cellPos = findCell(cellUp);

    if (cellPos && cellUp === lastValidCell) {
      createCellSpan();
    }
  });

  $(window.document).on('mouseup touchend', function(event) {
    mouseDown = false;
    //cellDown = cellOver = null;
    clearHighlights();
  });

  function findCell(cell) {
    var col, row;

    $cells.each(function(idx, el) {
      if (cell === el) {
        col = idx % numCols;
        row = Math.floor(idx / numCols);

        if (matrix[row][col] === 1) { // a rowspan already exists for this cell
          //console.log('found', row, col);
          col = null;
        }
        return false;
      }
    });

    return (col != null) ? {
      col: col,
      row: row
    } : null;
  }

  function highlightCells() {

    clearHighlights();

    if (endRow >= startRow) {
      for (var row = startRow; row <= endRow; row++) {
        if (matrix[row][startCol] !== 1) { // rowspan doesn't already exists for this cell
          var $thisCell = $cells.eq(row * numCols + startCol);
          $thisCell.addClass('highlight');
          lastValidCell = $thisCell[0];
        } else {
          endRow = row - 1; // found a blocking cell
          return false;
        }
      }
    } else {
      for (var row = startRow; row >= endRow; row--) {
        if (matrix[row][startCol] !== 1) { // rowspan doesn't already exists for this cell
          var $thisCell = $cells.eq(row * numCols + startCol);
          $thisCell.addClass('highlight');
          lastValidCell = $thisCell[0];
        } else {
          endRow = row + 1; // found a blocking cell
          return false;
        }
      }
    }
  }

  function clearHighlights() {
    $cells.removeClass('highlight');
  }

  function createCellSpan() {
    var sRow = Math.min(startRow, endRow);
    var eRow = Math.max(startRow, endRow);
    var rowSpan = eRow - sRow + 1;
    
    
    for (var row = eRow; row >= sRow; row--) {
      var $thisCell = $cells.eq(row * numCols + startCol);
      if (row === sRow)
        $thisCell.attr('rowspan', rowSpan).addClass('spanned');
      else 
        $thisCell.remove();
      matrix[row][startCol] = 1; // mark these cells as blocked
    } 

    /*for (var row = sRow; row <= eRow; row++) {
      var $thisCell = $cells.eq(row * numCols + startCol);
      $thisCell.addClass('spanned');
      matrix[row][startCol] = 1; // mark these cells as blocked
    } */
  }

});
.highlight {
  background-color: yellow;
}

.spanned {
  background-color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="myTable" border="1" cellpadding="10">
  <thead>
    <tr>
      <th></th>
      <th>A</th>
      <th>B</th>
      <th>C</th>
      <th>D</th>
      <th>E</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td class="rowHdr">1</td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td class="rowHdr">2</td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td class="rowHdr">3</td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td class="rowHdr">4</td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td class="rowHdr">5</td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td class="rowHdr">6</td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
  </tbody>
K Scandrett
  • 16,390
  • 4
  • 40
  • 65
  • This is awesome! Thank you! I've been struggling with this for a few days now – Adam M. Mar 04 '17 at 00:39
  • It took me a while too (~3hrs). A number of challenges to get right. But once I started and I figured I might as well finish it. – K Scandrett Mar 04 '17 at 00:41
  • I'm not gonna lie. This code is way more advanced than i can understand. But if I take the time to go through it carefully, I'm sure I can learn a lot. Thanks for the honest effort. I will likely use this as soon as I can wrap my head around what you did. – Adam M. Mar 04 '17 at 00:49
  • I suggest adding a lot of `console.log()`s throughout to aid understanding. A significant portion of the code is to make sure they don't collide with any existing row spans (row span info is stored in the array `matrix`), and can only select cells within the starting column. – K Scandrett Mar 04 '17 at 00:59
0

daver182 's code can also be seen in here http://jsfiddle.net/daver182/cB4UQ/1/

$(function () {
  var isMouseDown = false,
    isHighlighted;
 var currentCol;
  $("#our_table td")
    .mousedown(function () {
      isMouseDown = true;
      currentCol = this.getAttribute("data-col");
      $(this).toggleClass("highlighted");
      isHighlighted = $(this).hasClass("highlighted");
      return false; // prevent text selection
    })
    .mouseover(function () {
      if (isMouseDown) {
          if(currentCol === this.getAttribute("data-col")){
              $(this).toggleClass("highlighted", isHighlighted);
          }
      }
    })
    .bind("selectstart", function () {
      return false;
    })

  $(document)
    .mouseup(function () {
      isMouseDown = false;
    });
});
table td {
  width:100px;
  height:100px;
  text-align:center;
  vertical-align:middle;
  background-color:#ccc;
  border:1px solid #fff;
}

table td.highlighted {
  background-color:#999;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<table cellpadding="0" cellspacing="0" id="our_table">
  <tr>
    <td data-row="1" data-col="1">a</td>
    <td data-row="1" data-col="2">b</td>
    <td data-row="1" data-col="3">c</td>
  </tr>
  <tr>
    <td data-row="2" data-col="1">d</td>
    <td data-row="2" data-col="2">e</td>
    <td data-row="2" data-col="3">f</td>
  </tr>
  <tr>
    <td data-row="3" data-col="1">g</td>
    <td data-row="3" data-col="2">h</td>
    <td data-row="3" data-col="3">i</td>
  </tr>
</table>
  • Thanks for the response. I could already do this with my code before trying to span the rows. I might use a similar method to yours now. But this doesn't answer my question of how I can span rows by dragging. I want the actual rowspan="" attribute of the clicked td to change its value based on how far I drag. – Adam M. Mar 03 '17 at 19:12