Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automatically highlight "rest of day" in custom calendar

Tags:

javascript

I'm creating an event scheduler in vanilla JS. I'm currently working on the ability to highlight a time-span by "dragging" the mouse over the desired times. I've got it working fairly well, but I want the rest of the day to highlight automatically if the user starts dragging on one day (e.g Monday) over to the next (e.g Tuesday).

In other words, if you start highlighting on Monday at 03 am and drag over to Tuesday 05 am, the highlighted area should automatically be Monday 03-07 and Tuesday 00-05.

I almost got it working, but all these coordinates and calculations makes me dizzy. Any help or guidance would be most welcome.


This is the snippet I'm having issues with:

if((j >= (startCol-1) && j <= (col-1) && i >= (startRow-1) && i < (row-1))) {
  rows[i].children[j].classList.add('selected');
} else if((j > (startCol-1)) && (j <= (col-1)) && (i < (startRow-1))) {
  rows[i].children[j].classList.add('selected');
} else if((j < (col-1) && (j >= (startCol-1)) && (i >= (row-1)))) {
  rows[i].children[j].classList.add('selected');
} else {
  rows[i].children[j].classList.remove('selected');
}

Here's a full snippet

const table = document.querySelector('.table');
const rows = document.querySelectorAll('.row');
const sqSize = 28;
let selValue = 'p1';
let didMouseMove = false;
let startRow;
let startCol;

const cb = (e) => {
	didMouseMove = true;
	const x = e.clientX - table.offsetLeft;
	const y = e.clientY - table.offsetTop;
  const row = Math.round(y / sqSize);
  const col = Math.round(x / sqSize);

  for(let i=0; i < rows.length; i++) {
  	for(let j=0; j < rows[i].children.length; j++) {
    	if((j >= (startCol-1) && j <= (col-1) && i >= (startRow-1) && i < (row-1))) {
    		rows[i].children[j].classList.add('selected');
      } else if((j > (startCol-1)) && (j <= (col-1)) && (i < (startRow-1))) {
      	rows[i].children[j].classList.add('selected');
      } else if((j < (col-1) && (j >= (startCol-1)) && (i >= (row-1)))) {
      	rows[i].children[j].classList.add('selected');
      } else {
      	rows[i].children[j].classList.remove('selected');
      }
    }
  }
}

table.addEventListener('mousedown', (e) => {
  startRow = Math.round((e.clientY - table.offsetTop) / sqSize);
	startCol = Math.round((e.clientX - table.offsetLeft) / sqSize);
  table.addEventListener('mousemove', cb);
});
table.addEventListener('mouseup', (e) => {
	const x = table.querySelectorAll('.selected');
  for (let i = 0; i < x.length; i++) {
  	x[i].className = selValue;
  }
	if(!didMouseMove && !e.target.classList.contains('row')) {
  	e.target.classList.toggle(selValue);
  }
	didMouseMove = false;
  table.removeEventListener('mousemove', cb);
});
document.getElementById('sel').addEventListener('change', (e) => {
	selValue = e.target.value;
});
body {
  margin: 0;
  padding: 0;
}

.head {
  font-size: 0;
  margin-left: 32px;
}

.head > div {
  font-size: 12px;
  display: inline-block;
  width: 25px;
  margin: 0 2px;
  text-align: center;
}

.pre {
  display: inline-block;
  font-size: 0;
  vertical-align: top;
  width: 32px;
  margin-top: -8px;
}

.pre > div {
  height: 25px;
  margin: 2px 0;
  font-size: 12px;
  display: inline-block;
}

.table {
  display: inline-block;
}

.wrapper {
  display: inline-block;
  position: relative;
}

.row {
  font-size: 0;
}

.row > div {
  display: inline-block;
  border: 1px solid black;
  margin: 1px;
  width: 25px;
  height: 25px;
}

.p1 {
  background: green;
}

.p2 {
  background: blue;
}

.p3 {
  background: orange;
}

.selected {
  background: grey;
}
<div>
  <select id="sel">
     <option value="p1">Profile 1</option>
     <option value="p2">Profile 2</option>
     <option value="p3">Profile 3</option>
   </select>
 </div>
 
 <div class='wrapper'>
   <div class='head'>
     <div>Mon</div>
     <div>Tue</div>
     <div>Wed</div>
     <div>Thu</div>
     <div>Fri</div>
     <div>Sat</div>
     <div>Sun</div>
   </div>
   <div>
     <div class='pre'>
       <div>00:00</div>
       <div>00:30</div>
       <div>01:00</div>
       <div>01:30</div>
       <div>02:00</div>
       <div>02:30</div>
       <div>03:00</div>
       <div>03:30</div>
       <div>04:00</div>
       <div>04:30</div>
       <div>05:00</div>
       <div>05:30</div>
       <div>06:00</div>
       <div>06:30</div>
       <div>07:00</div>
     </div>
     <div class='table'>
       <div class='row'>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
       </div>
       <div class='row'>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
       </div>
       <div class='row'>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
       </div>
       <div class='row'>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
       </div>
       <div class='row'>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
       </div>
       <div class='row'>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
       </div>
       <div class='row'>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
       </div>
       <div class='row'>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
       </div>
       <div class='row'>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
       </div>
       <div class='row'>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
       </div>
       <div class='row'>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
       </div>
       <div class='row'>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
       </div>
       <div class='row'>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
       </div>
       <div class='row'>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
         <div></div>
       </div>
     </div>
   </div>
 </div>

and jsfiddle fork: http://jsfiddle.net/a2v9rd50/1/

like image 461
Chris Avatar asked Oct 09 '18 13:10

Chris


1 Answers

I wrote this, that needs to be tweaked and refactored, but the selection "works".

I dynamically generated an ugly a stylished calendar. Each cell has an id built like this : day_halfhour. day being the x coordinate, halfhour being the y one. The cell for monday at 00:00 has id 0_0. Sunday 23:30 has 6_47.

There's 2 events listener on each cell.

One for selection starting (mousedown) that sets up the starting coordinates (day + hour) startPoint and raises the flag selecting

The other one is used for the selection (mouseover). It's executed only when the flag selecting is true. It sets the ending coordinate (day + hour) coor with the current cell id (x_y)

There's a 3rd event, on document (mouseup) that displays in console the starting and ending coordinates.

It's up to you to use that final event to design the selected dates and manage datas.

function changeColor(x, y, state)
{
    let cell = document.getElementById(x + '_' + y);
    if (cell)
      cell.className = state;
}

let tbl = document.createElement('table');
let container = document.getElementById('myTable');
let startPoint = null;
let endPoint = null;
let selecting = false;
for (let i = 0; i < 48; ++i)
{
    let tr = document.createElement('tr');
    for (let j = 0; j < 7; ++j)
    {
        let td = document.createElement('td');
        td.setAttribute('id', j + "_" + i);
        td.innerHTML = "A";
        td.addEventListener('mousedown', (e) =>
        {
            let coortemp = e.srcElement.id.split('_');
            startPoint = [coortemp[0], coortemp[1]];
            selecting = true;
            changeColor(coortemp[0], coortemp[1], "selecting");
        });
        td.addEventListener('mouseover', (e) =>
        {
            if (selecting)
            {
                endPoint = e.srcElement.id.split('_');
                for (let x = 0; x < 7; ++x)
                {
                    for (let y = 0; y < 48; ++y)
                    {
                        //We're out of range concerning selection. background is white
                        if (x < startPoint[0]
                            || x > endPoint[0])
                            changeColor(x, y, "deselected");
                        else
                        {
                            //In the case where we're "painting" the first day
                            //of the selection.
                            if (x == startPoint[0])
                            {
                                /*
                                 * This checks if start and end are the same day.
                                 * to avoid "painting" too much hours
                                 * Or if days are differents, so we can "paint" until midnight
                                 */
                                if (y >= startPoint[1]
                                    && ((startPoint[0] == endPoint[0]
                                            && y <= endPoint[1])
                                        || startPoint[0] != endPoint[0]))
                                {
                                    changeColor(x, y, "selecting");
                                }
                                else
                                {
                                    changeColor(x, y, "deselected");
                                }
                            }
                            //last day of selection ...
                            else if (x == endPoint[0])
                            {
                                //... before or at last hour
                                if (y <= endPoint[1])
                                {
                                    changeColor(x, y, "selecting");
                                }                                    
                                else
                                {
                                    changeColor(x, y, "deselected");
                                }
                            }
                            //There, we're definitely in range and we can blindly "paint"
                            else
                            {
                                changeColor(x, y, "selecting");
                            }
                        }
                    }
                }
            }
        });
        tr.appendChild(td);
    }
    tbl.appendChild(tr);
}
container.appendChild(tbl);
document.addEventListener('mouseup', () =>
{
    if (selecting)
    {
        if (!endPoint)
            endPoint = startPoint;
        console.log('final selection : from day ' + startPoint[0] + ' half-hour * ' + startPoint[1] + ' to day ' + endPoint[0] + ' half-hour * ' + endPoint[1]);
        selecting = false;
        let days = document.getElementsByClassName("selecting");
        
        while (days.length)
          days[0].className = "selected";
    }
});
.selecting
{
  background-color: #A9A9A9;
}

.deselected
{
  background-color: #FFFFFF;
}

.selected
{
  background-color: #00FF00;
}
<div id="myTable">
</div>
like image 140
Cid Avatar answered Oct 17 '22 21:10

Cid