What is the best way to implement a hover effect on multiple rows within an HTML table using Angular?

I am currently working on developing a table preview feature to display events. I previously sought assistance here regarding positioning elements within the table and successfully resolved that issue. Applying the same principles, I am now attempting to create a preview based on specific hour durations corresponding to each column location in order to populate an events list.

However, I am encountering problems with the preview functionality as it inconsistently appears and disappears. Despite spending several hours trying to troubleshoot the issue, I have not been able to find a solution. Additionally, once the preview is displayed, clicking on the event should save it.

What is causing the erratic blinking behavior of the preview?

Here is the code I'm using:

Stackblitz: https://stackblitz.com/edit/angular-ivy-k22v8c?file=src/app/app.component.ts

HTML:

<div class="calendar-container">
  <table
    (mouseenter)="activatePreview()"
    (mouseleave)="deactivatePreview()"
    (mousemove)="calculatePreview($event)"
  >
    <tr>
      <th></th>
      <th class="cell-place" *ngFor="let place of places">{{ place }}</th>
    </tr>
    <tr *ngFor="let hour of hours">
      <td class="cell-hour">{{ hour }}</td>
      <td class="cell" *ngFor="let place of places"></td>
    </tr>
  </table>
  <div *ngIf="previewActive">
    <div class="preview" [ngStyle]="preview.position">
      <p>{{ preview.location }}</p>
    </div>
  </div>
  <div *ngFor="let event of events">
    <div class="event" [ngStyle]="event.position">
      <p>{{ event.location }}</p>
    </div>
  </div>
</div>

TS:

export class AppComponent implements AfterViewInit {
  hours = [
    '8:00 AM',
    '9:00 AM',
    '10:00 AM',
    '11:00 AM',
    '12:00 PM',
    '1:00 PM',
    '2:00 PM',
    '3:00 PM',
    '4:00 PM',
    '5:00 PM',
  ];
  places = ['P1', 'P2', 'P3', 'P4', 'P5'];
  events: any = [];

  cellWidth = 0;
  cellWidthHour = 0;
  cellHeight = 0;

  previewActive = false;
  previewDuration = 2;
  preview: any = {};
  currentCell = null;

  activatePreview() {
    this.previewActive = true;
  }

  deactivatePreview() {
    this.previewActive = false;
    this.currentCell = null;
    this.preview = {};
  }

  calculatePreview(event: any) {
    if (!this.previewActive) {
      return;
    }

    // Get the position of the cursor
    const x = event.clientX;
    const y = event.clientY;

    // Calculate the column (location) of the preview
    const columns = document.getElementsByClassName('cell-place');
    let column;
    for (const col of Array.from(columns)) {
      if (
        col.getBoundingClientRect().left <= x &&
        x <= col.getBoundingClientRect().right
      ) {
        column = col;
        break;
      }
    }

    if (!column) {
      return;
    }
    console.log(column);

    // Calculate the start and end times of the preview
    const rows = document.getElementsByClassName('cell-hour');
    let startIndex = -1;
    let endIndex = -1;
    for (let i = 0; i < rows.length; i++) {
      const row = rows.item(i);
      if (
        row.getBoundingClientRect().top <= y &&
        y <= row.getBoundingClientRect().bottom
      ) {
        startIndex = i;
        endIndex = i + this.previewDuration - 1;
        break;
      }
    }
    console.log(startIndex, endIndex);

    // Check if the preview goes beyond the limit of the table
    if (startIndex === -1 || endIndex === -1 || endIndex >= rows.length) {
      return;
    }

    // Check if the cursor is in a new cell
    const newCell = startIndex + '-' + endIndex;
    console.log(newCell);
    if (newCell === this.currentCell) {
      return;
    }
    this.currentCell = newCell;

    // Update the preview based on the calculations
    const startTime = this.hours[startIndex];
    const endTime = this.hours[endIndex];
    const location = column.innerHTML;

    this.preview = { startTime, endTime, location };
    this.preview.position = this.calculateEventPosition(
      startTime,
      endTime,
      location
    );
    console.log(this.preview);
  }

  constructor() {}

  ngAfterViewInit() {
    // this.getCellSize();
    this.createEvent('11:00 AM', '12:00 PM', 'P3');
  }

  createEvent(startTime: string, endTime: string, location: string) {
    const event: any = { startTime, endTime, location };
    event.position = this.calculateEventPosition(startTime, endTime, location);
    this.events.push(event);
  }

  calculateEventPosition(startTime: string, endTime: string, location: string) {
    const rect = document
      .getElementsByTagName('table')[0]
      .getBoundingClientRect();
    const columns = document.getElementsByClassName('cell-place');
    const rows = document.getElementsByClassName('cell-hour');
    const column = Array.from(columns).find((x) => x.innerHTML == location);
    const start = rows.item(this.hours.indexOf(startTime));
    const end = rows.item(this.hours.indexOf(endTime));

    const left = column.getBoundingClientRect().left - rect.left;
    const top = start.getBoundingClientRect().top - rect.top;
    const width = column.getBoundingClientRect().width;
    const height =
      end.getBoundingClientRect().top - start.getBoundingClientRect().top;

    return {
      height: height + 'px',
      top: top + 'px',
      left: left + 'px',
      width: width + 'px',
    };
  }
}

CSS:

.calendar-container {
  position: relative;
}

table {
  border-collapse: collapse;
  width: 100%;
}

td,
th {
  border: 1px solid #dddddd;
  text-align: center;
}

table td {
  height: 25px;
}

.event {
  box-sizing: border-box;
  position: absolute;
  background-color: #f2f2f2;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.preview {
  box-sizing: border-box;
  position: absolute;
  background-color: yellow;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9;
}

I have made progress in setting up the preview mechanism but it's not functioning correctly, exhibiting flickering behavior and occasional delays. The preview should strictly pertain to hourly increments, and I am uncertain about how to implement click-to-save functionality based on event duration correlated to the location column.

Answer â„–1

There seems to be a bug in this section of the code

<table
    (mouseenter)="activatePreview()"
    (mouseleave)="deactivatePreview()"
    (mousemove)="calculatePreview($event)"
>...</table>

When moving the mouse from one cell to another, the mouseenter and mouseleave event bindings are triggered simultaneously, causing the mouseleave event to unset a variable called currentCell.

To solve this issue, you can remove the mouseenter and mouseleave event listeners, as well as the specific code block within the calculatePreview method mentioned below:

 // if (newCell === this.currentCell) {
 //   return;
 // }

Please test this solution and let me know if it fixes the problem.

Answer â„–2

Your code seems a bit too complex. If you just want to obtain the position of the "hovered cell," you can simplify it by using the following approach:

<!--use the template reference variable #cell-->
<td #cell class="cell" *ngFor="let place of places"
        (mouseenter)="getPosition(cell)"></td>

  getPosition(el:HTMLElement)
  {
    console.log(el.getBoundingClientRect())
  }

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

personalizing material-ui component styles – set select component color to be pure white

I am looking to implement a dropdown menu using material-ui components (refer to https://material-ui.com/components/selects/). To do so, I have extracted the specific component from the example: Component return <div> <FormControl variant="outli ...

Using jQuery, target the specific elements that have certain data attributes assigned to them

Is there a way to target elements with a specific data attribute and a unique class using jQuery? For instance, I want to select these elements: <div data-roomid="55" data-status="NoData"></div> <div data-roomid="55" data-status="Open"> ...

Can a form be submitted to two different pages based on the button name?

I have a form that directs to another page and performs various database operations. <form id="form1" method="post" action="goes_to_page1.php" onsubmit="return checkformvalidation();"> <input type="text" id="field1" name="field1" onblur="checkva ...

Tips for implementing a personalized Circuit Breaker in Node.js that monitors request volume

Currently, I am exploring the most effective way to implement a circuit breaker based on the number of requests served in a Typescript/express application rather than fail percentage. Given that the application is expected to accommodate a large volume of ...

Arranging nested divs in relation to one another in a specific position

This code represents a chart in HTML. The goal is to have the second (middle) div start where the first (top) div ends, which has a width of 75px. To achieve this, the margin-left for the middle div is set to 75px in the following styles. Following the sam ...

Creating a list of components for drag and drop with Angular CDK is a straightforward process that involves following

I am attempting to use Angular's CDK drag and drop functionality to create a list of components that can be rearranged. However, I am encountering an issue where the components are not being displayed correctly. In my App.component.ts file: impo ...

javascript doesn't execute the php script

Hello everyone, I've been working on a project for quite some time and I’ve encountered an issue that I can't seem to solve. Hopefully, you can help me out with this. I have a digital LED strip controlled by an Arduino, which in turn is control ...

Showcase Ionic Validation - exhibit error messages reminiscent of material design

I've been working on a Login and Registration Page in Ionic 5. I wanted to showcase error messages beneath the input text field like shown in this example https://i.stack.imgur.com/d83ZV.png Thus, I integrated Angular Responsive Forms into my projec ...

JavaScript code to retrieve an image from an <img> tag's source URL that only allows a single request and is tainted due to cross-origin restrictions

I have an image displayed in the HTML DOM. https://i.stack.imgur.com/oRgvF.png This particular image (the one with a green border) is contained within an img tag and has a URL as its source. I attempted to fetch this image using the fetch method, but enc ...

Display the data in ngx-charts heat map

I have been utilizing ngx charts to display data in a Heat Map. The implementation is smooth without encountering any issues. View Working Example | Heat Map However, I am interested in displaying the values of each grid rather than just on mouse hover. ...

What is the proper way to utilize a function in C# that integrates with a window form using TypeScript?

I am currently working on a code that is in c# and utilizes a web browser. My goal is to convert the existing JavaScript code to Angular 7 and Typescript. Below is the c# code and the corresponding JavaScript code used to access the c# function from JavaS ...

Is it possible to save the location of a draggable box using CSS3?

I'm trying to figure out how to store the position of a box in a fluid sortable row. Essentially, I want the position to be remembered when a user drags a box from category 1 to category 2, even after refreshing the page. Below is the HTML code for t ...

When using Google Chrome, I noticed that it automatically creates an <object> element next to my <video> tag

Recently, I encountered an issue with a video tag that included both mp4 and ogv files. While the video played smoothly on most devices and browsers, Google Chrome started creating a strange black box near the video in certain cases. This new tag appeare ...

How can AngularJS focus on the last element using ng-repeat and md-focus?

, there is a code snippet that utilizes AngularJS ng-repeat directive to iterate through 'items' and 'md-autofocus' attribute to focus on the last element. The code is contained within an md-dialog with vertical scrolling functionality. ...

Automatically populate select boxes with values from a different source using PHP

I'm in the process of setting up automatic population for 2 select boxes on a website. When a user chooses a class, the second select box automatically displays the main specialization of that class. If the user selects Tank (for example), the third ...

Executing function with ng-click in AngularJS only on the second click of the button

Currently, I am studying AngularJS and working on an exercise that involves using the ng-click function. Strangely, I am only able to view the result after clicking upload for the second time. I am trying to display my json content but it's not workin ...

Clearing all data in a form upon opening

Within a single portlet, I have organized two forms under separate tabs. What I am aiming for is that whenever I switch to tab 2, all fields within its associated form should automatically be cleared without the need for a button click. Even after attempti ...

Exploring the boundaries of HTML data manipulation using JavaScript or jQuery

In my HTML (within a Twig template), there is the following code snippet: <li id="{{folder.id}}" data-jstree='{"icon":"glyphicon glyphicon-tags", "type":"folder"}' ><a href="#">{{folder.name}}</a> I'm attempting to extrac ...

Having trouble with rendering components in React and Bootstrap?

Attempting to display basic Bootstrap components using React. This corresponds to the index.html file: <!doctype html> <html> <head> <script src="http://code.jquery.com/jquery-2.1.3.min.js"></script> <script src="j ...

Tips for utilizing PINO to write logs to both file and console

I have successfully implemented logging to a log file using Pino. However, I am wondering if there is a way to also log to the console in order to manage the frequency of 'console.log()' calls. Node Version : 21.6.1 Typescript Version : 5.3.3 Pi ...