Selecting items in an HTML table using a drag-and-drop feature for selecting square or rectangular sections with colspan and rowspan

I am currently working on an implementation for selecting cells in a table. The challenge I am facing is that the cells in question can have a colspan or rowspan, which means that the selection is not restricted to a square or rectangular shape. For example, if you try selecting "1-3" and "2-3", it should also automatically select "1-4". I have reviewed a similar question on this topic, but so far, I have not been able to make any solution work. Do you have any insights on how this could be implemented?

Link: Access the Working Code

HTML

<table drag-select drag-select-ids="ids">
      <tr>
        <td id="td-1-1">1-1</td>
        <td id="td-1-2">1-2</td>
        <td id="td-1-3">1-3</td>
        <td id="td-1-4">1-4</td>
      </tr>
      <tr>
        <td id="td-2-1">2-1</td>
        <td id="td-2-2">2-2</td>
        <td id="td-2-3" colspan="2">2-3</td>
      </tr>
      <tr>
        <td id="td-3-1">3-1</td>
        <td id="td-3-2">3-2</td>
        <td id="td-3-3">3-3</td>
        <td id="td-3-4">3-4</td>
      </tr>
      <tr>
        <td id="td-4-1">4-1</td>
        <td id="td-4-2">4-2</td>
        <td id="td-4-3">4-3</td>
        <td id="td-4-4">4-4</td>
      </tr>
    </table>

JavaScript

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.ids = [];
});

app.directive('dragSelect', function($window, $document) {
  return {
    scope: {
      dragSelectIds: '='
    },
    controller: function($scope, $element) {
      var cls = 'eng-selected-item';
      var startCell = null;
      var dragging = false;

      function mouseUp(el) {
        dragging = false;
      }

      function mouseDown(el) {
        dragging = true;
        setStartCell(el);
        setEndCell(el);
      }

      function mouseEnter(el) {
        if (!dragging) return;
        setEndCell(el);
      }

      function setStartCell(el) {
        startCell = el;
      }

      function setEndCell(el) {
        $scope.dragSelectIds = [];
        $element.find('td').removeClass(cls);
        cellsBetween(startCell, el).each(function() {
          var el = angular.element(this);
          el.addClass(cls);
          $scope.dragSelectIds.push(el.attr('id'));
        });
      }

      function cellsBetween(start, end) {
        var coordsStart = getCoords(start);
        var coordsEnd = getCoords(end);
        var topLeft = {
          column: $window.Math.min(coordsStart.column, coordsEnd.column),
          row: $window.Math.min(coordsStart.row, coordsEnd.row),
        };
        var bottomRight = {
          column: $window.Math.max(coordsStart.column, coordsEnd.column),
          row: $window.Math.max(coordsStart.row, coordsEnd.row),
        };
        return $element.find('td').filter(function() {
          var el = angular.element(this);
          var coords = getCoords(el);
          return coords.column >= topLeft.column
              && coords.column <= bottomRight.column
              && coords.row >= topLeft.row
              && coords.row <= bottomRight.row;
        });
      }

      function getCoords(cell) {
        var row = cell.parents('row');
        return {
          column: cell[0].cellIndex, 
          row: cell.parent()[0].rowIndex
        };
      }

      function wrap(fn) {
        return function() {
          var el = angular.element(this);
          $scope.$apply(function() {
            fn(el);
          });
        }
      }

      $element.delegate('td', 'mousedown', wrap(mouseDown));
      $element.delegate('td', 'mouseenter', wrap(mouseEnter));
      $document.delegate('body', 'mouseup', wrap(mouseUp));
    }
  }
});

CSS

[drag-select] {
  cursor: pointer;
 -webkit-touch-callout: none;
 -webkit-user-select: none;
 -khtml-user-select: none;
 -moz-user-select: none;
 -ms-user-select: none;
 user-select: none;
}

[drag-select] .eng-selected-item {
  background: blue;
  color: white;
}

td {
  padding: 10px;
  border: 1px solid gray;
}

Answer №1

To select cells within a rectangle, I utilized the x and y coordinates of the start and end cell. By calculating all cells inside the corresponding rectangle, including those partially covered, I then determined the bounding rectangle for these cells. This process is repeated until the selection no longer expands.

By the way, the rectangleSelect function is adapted from a solution found on Stack Overflow.

Furthermore, the script now supports handling rowspan and other scenarios, such as selecting cell ranges like [3-2, 2-3]. Check it out here: https://i.sstatic.net/DjG8f.png

For a live demonstration and code, visit this link: http://plnkr.co/edit/8wZvcU1SgmieStsqg3lD?p=preview

Here are the HTML, CSS, and JS snippets:

Your HTML, CSS, and JavaScript code goes here...

Feel free to explore the provided code segments to understand the selection logic.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Alter the style type of a Next.js element dynamically

I am currently working on dynamically changing the color of an element based on the result of a function //Sample function if ("123".includes("5")) { color = "boldOrange" } else { let color = "boldGreen" } Within my CSS, I have two clas ...

The functionality of Flatbutton(Input handler) appears to be malfunctioning

I am having trouble with the file uploader from Material-UI. It doesn't seem to be working properly when I try to select a file. Does anyone have any suggestions on how to handle the input from the file selector? <FlatButton icon={< ...

Retrieve the report information and present it in a HTML data table using a REST API

My understanding of REST API and Javascript is limited, but I now find myself needing to interact with a third-party company's REST API for email reporting purposes. The data can be accessed through a GET method using a URL with a specific TOKEN: {pl ...

Modal Pop-ups Failing to Open on Subsequent Attempts

My table consists of buttons on the right-hand side for a menu. The first button in the menu is supposed to open a modal box upon clicking. However, the first buttons in the subsequent rows do not function as expected and fail to open the modal. Refer to t ...

A guide to transforming rows into columns with CSS

Hello, I am trying to convert rows into columns using CSS. Currently, my code looks like this: <style> .flex-container { max-width: 500px; width: 100%; display: flex; flex-wrap: wrap; } .flex-item { ...

Using an array in jade rendering

I'm currently working on a node.js server using express and I'm facing an issue with passing an array to jade rendering. Here's the code snippet from my node.js file: router.get('/render', function(req, res) { var t; var ...

What is the best way to transform this SQL query into Sequelize syntax?

As I work on a SQL Query, I've come across this issue: select min(date_part('year', "date")) from "Arts" a I need to convert it into a sequelize query. Any assistance would be much appreciated. Art.findOne({ attributes: [[sequelize.fn(&ap ...

Create a new array containing the keys from an array of objects

My task involves extracting the key puppies from an array of objects and returning it in a new array: The input is an array of dogs structured like this: [ {breed: 'Labrador', puppies: ['Fluffy', 'Doggo', 'Floof&ap ...

Exploring the art of JSON interpretation within Highcharts

Below is an example snippet that utilizes static data: Highcharts.chart("container", { title: { text: "Highcharts pie chart" }, xAxis: { categories: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Ju ...

Develop a feature within an Angular directive to implement a button that can delete the directive completely

If I have an element called "filterList" containing angular directives in the form of <filter-item>, and I want each <filter-item> to include a delete button that removes it from the DOM when clicked, how can I achieve this? <div class="fil ...

Exploring the power of developing AngularJS applications using Typescript and the app

I am currently working on a basic application that utilizes Typescript and AngularJS. In my app, I have used app.js to register my controller and some route parameters: /// <reference path="../scripts/typings/angularjs/angular.d.ts" /> /// <refer ...

Unable to locate the element within the specified div using xpath or css selector

I have attempted to retrieve the output "January 27" using this code: task_offer_deadline = driver.find_element_by_xpath('//*[@id="table"]/div[2]/div/a/div[1]/span[2]/div/em').text The xpath was obtained from Google Chrome Console. Here is a sn ...

Utilize the function of express

The following code is not behaving as expected: var express = require('express'); var app = express(); app.use(function(req, res, next) { console.log('first statement'); next(); }, function (req, res, next) { console.log('se ...

The conclusion of jQuery's nested animation

Functioning, But Page Transitions Inconsistent What I Believe to be the Correct Approach, But Event Triggering Issue Currently, I am experimenting with page transitions and utilizing bind() to detect the end of the CSS event transition. When a class is ad ...

Generating a JavaScript JSON object by iterating through a JSP bean with the help of JSTL's c:forEach

Two Java classes are defined as follows: public class Abc{ private List<SomeOtherClass> someClass; private String k; private String m; } public class SomeOtherClass{ private String a; private String b; private String c; } On a ...

What sets Import apart from require in TypeScript?

I've been grappling with the nuances between import and require when it comes to using classes/modules from other files. The confusion arises when I try to use require('./config.json') and it works, but import config from './config.json ...

What steps can be taken to ensure Vue application admin page contents are not displayed without proper authentication?

By implementing role-based authentication in Vue app, we can efficiently manage routes and components visibility between regular users and administrators. As the number of roles increases, the application size grows, making it challenging to control CRUD ...

Create an HTML table with a static header, adjustable columns, and the ability to resize to a width smaller than the content

Looking to create a unique web application with a table that has a fixed, styled header, resizable columns, and the ability to resize the columns without truncating content. Additionally, I want the body of the table to scroll if it cannot all fit on the p ...

How can I utilize match props in React JS with the Context API?

Currently working on a basic application that utilizes context API, fetch, and react hooks for all components except the context API component due to ongoing learning of hooks. The challenge lies in incorporating the match prop within the context API prov ...

The data from Restangular is currently being divided

RestService.one('suppliers', 'me').getList('websites').then( (data) -> $scope.websites = data $scope.websites.patch() ) I'm conducting a quick test with this code snippet. When mak ...