Preventing Memory Leaks when removing DOM elements - javascript

I've been developing in Javascript for a while but the issue I am having with memory leaks is a little over my head. Apologies as this my be a long question but any help will be greatly appreciated.
I have a custom made table in a calendar format #sxpSolutionTblWrap which is dynamically populated using a template with details of each day. By default I load in 2 weeks, without weekends, so I populate 10 days worth of DOM elements.
Clicking on a day calls a function populateInfoPanel, in this function I call another function loadSingleSolutionRow where I find if a previous day has been selected in table and remove it, then populate it again with the basic info as if it weren't selected.
loadSingleSolutionRow
loadSingleSolutionRow: function(dow, dateValue, click, columnId) {
var self = this;
if (click === true) {
$('.sxpTableCol[data-colid="' + dow + '"]').empty();
}
//do stuff
var columnName = click === true ? '.sxpTableCol[data-colid="' + dow + '"]' : '#solCol' + columnId;
if (click === true) {
$(columnName).replaceWith(self.solutionTableRow({
dayId: dow,
date: selectedDate,
dateValue: dateValue,
statusClass: statusClass,
statusIconClass: statusIconClass,
trainerCountEarly: trainerCountEarly,
trainerListEarly: trainerListEarly,
roomCountEarly: roomCountEarly,
roomListEarly: rooomListEarly,
trainerCountLate: trainerCountLate,
trainerListLate: trainerListLate,
roomCountLate: roomCountLate,
roomListLate: rooomListLate,
protip: protip,
statusText: statusText,
}));
} else {
$(columnName).append(self.solutionTableRow({
dayId: dow,
date: selectedDate,
dateValue: dateValue,
statusClass: statusClass,
statusIconClass: statusIconClass,
trainerCountEarly: trainerCountEarly,
trainerListEarly: trainerListEarly,
roomCountEarly: roomCountEarly,
roomListEarly: rooomListEarly,
trainerCountLate: trainerCountLate,
trainerListLate: trainerListLate,
roomCountLate: roomCountLate,
roomListLate: rooomListLate,
protip: protip,
statusText: statusText,
}));
}
//Event listener for clicking on day
$('.sxpTableCol').off().on('click', '.sxpTableHeader', function (e) {
//populateInfoPanel
});
}
I then proceed to add events of each day to load onto the calendar in a for loop, that remove DOM elements previously in day and replaces it with new DOM element in the populateInfoPanel Function
populateInfoPanel
for (var j = 0; j < duration; j++) {
var dayCount = j + 1;
if (dayId !== undefined && daySolutions.Days[dayId].Solution !== undefined) {
//Getting day of solution in calendar grid
var daySolution = daySolutions.Days[dayId].Solution[j];
var solutionDayIndex = daySolution.DayIndex;
var solutionWeekIndex = daySolution.WeekNum;
var startWeek = moment(self.dto.startDate, 'YYYY-MM-DDTHH:mm').diff(moment(this.settings.findWhere({
Key: 'StartDate'
}).get('Value'), 'YYYY-MM-DD').startOf('isoWeek'), 'weeks') + 1;
var weekIndex = solutionWeekIndex - startWeek;
//var day = parseInt(dayId) + j;
var day = solutionDayIndex + (weekIndex * self.numDays);
//DO STUFF -- GETTING ROOM & TRAINER INFORMATION TO APPEND TO DOM
//DO STUFF -- GETTING POSITION OF ELEMENT ON THAT DAY
//DO STUFF -- GETTING LABELS THAT GO ONTO HEADER OF DAY
//APPEND DAY DETAILS TO CALENDAR
$(shiftData + day + '"]').append(self.selectedItemTpl({
dayCourse: dayCourse,
trainerList: trainerLabel,
roomList: roomLabel,
dayId: dayInfoId,
solutionId: j,
shiftId: shiftIndex,
day: day,
trainerIconClass: trainerIconClass,
roomIconClass: roomIconClass,
}));
}
}
The problem I am having is that when I click on a day and then another and another, it takes a little longer each time. This is fine when a solution only have 1 - 5 days on a 2 week grid, however it is very slow when a solution is 20 days long on a 5 week grid and the time when clicking through each day is more noticable.
The populateInfoPanel is about 500 lines long, unsure if that has anything to do with it. I am unsure if my method of removing elements isn't actually removing them from memory, if this is the case, how can I do this more effectively? Any help would be greatly appreciated.
From a 10 day solution on a grid of 5 weeks (25 days worth of DOM elements on Grid), here is the timeline view of clicking between days for 30 seconds, as you can see the first click takes 8 tenths of second, whereas the final click takes 1.5 seconds and gets more noticeable the more you click.
Below are some heap snapshots during 30 seconds of clicking between days, if you need to see anything further to get a better idea of what may be causing this issue, it would be very helpful
Before
Comparison

Related

How to fetch data by weeks and have it display by switching between weeks?

I want to code something like this where you can see the weeks and switch between them. At the moment I wrote these days in HTML, so it is not dynamic. I am new to this, so I don't know what JS library can do something like this. I managed to find something that gives you the current week. However, I don't think this is the right approach.
var week = {
period: null,
load: function () {
var d = new Date(); // today, now
week.period = d.setDate(d.getDate() + ((7 - d.getDay()) % 7 + 1) % 7);
document.getElementById("date").innerHTML = dayjs( week.period).format("DD/MMM/YYYY");
},
};
week.load();
<h1 id="date"></h1>
This way I get the next Monday. How would you go from here? I want to first get the weeks done. I have the fetch under control, as I have JSON data.

How to detect 'current week' for render days conditionally?

I discovered the dayRender function today (in the v4.0.0beta4 of fullCalendar.io).
My goal is render the previous weeks with gray background, the current week with white background e the future weeks with a third backgroundcolor.
Is there something in fullcalendar.io objects that can help me?
Using
dayRender: function(dayRenderInfo) {
console.log( $(dayRenderInfo.el).data('date') );
return dayRenderInfo.el;
}
I know that dayRenderInfo contains el, so using jQuery I can read $(el).data('date') to retrieve the date of the rendered day 'cell'.
But then, in js, how to check it, for example '2019-03-20' is the current week or past or future ?
I posted the question using fullcalendar tag because I hope there is an helper proprerty or similar, otherwiser, anyway, a plain js solution is very appreciated.
My solution is use the dayRender function of fullCalendar.io (actually #v4.0.1)
The function receive an HTML element already rendered. But you can intercept and manipulate it.
I decided to append an attribute, data-date, to the element so I can check it at runtime.
Note: I'm using jQuery.
dayRender: function(dayRenderInfo) {
// Make a Date object from current rendered element
const day = dayRenderInfo.el;
const date_str_of_this_day = $(day).data('date');
const this_day = new Date(date_str_of_this_day);
const today_string = new Date().toISOString().slice(0, 10);
// 0 (Sunday) ... 6 (Saturday)
let number_of_weekday = this_day.getDay();
if (number_of_weekday ==0) {
// I shift to adapt to italian week
// 1 (Monday) ... 7 (Sunday)
number_of_weekday = 7;
}
// From today's date object, I can find monday
let first = this_day.getDate() - number_of_weekday + 1;
const monday_date = new Date(this_day.setDate(first));
const monday_string = monday_date.toISOString().slice(0, 10);
// From monday's date object I can find sunday
let last = monday_date.getDate() + 6;
const sunday_date = new Date(this_day.setDate(last));
const sunday_string = sunday_date.toISOString().slice(0, 10);
if (sunday_string < today ) {
// the current day being renderer is AFTER current week
dayRenderInfo.el.style.backgroundColor = '#ededed';
} else if (today < monday_string ) {
// the current day being renderer is BEFORE current week
dayRenderInfo.el.style.backgroundColor = '#f9e9d7';
} else {
// the current day being renderer is PART OF curremt week
dayRenderInfo.el.style.backgroundColor = 'white';
}
// return altered html rendered
return dayRenderInfo.el;
},

Calculate new date after calendar event drop

I have a JavaScript/Math question.
I am stuck with one task for two days now and I guess I am complete idiot as I can't figure it out...Screenshot
I am creating a week calendar with shifts from 7am untill 8pm., but I can have shift which is for example 2 days long (or more).
The problem is that I can drag and drop the calendar event on the calendar and then I need to calculate new dateTo from dateFrom which I get from the div I placed it on.
The issues is that when I try to drag and drop the item to another time I need to place dateFrom to whenever I dragged it, but then I need to calculate hours so I get the same amount of time, but the problem is when the event is stretched over multiple days I need the event to finish next date after 7 am and not in the middle of the night. For example I had event from 3pm to 5pm of next day and then I moved it to 7pm of next day so I need the event to finish at 9 am of next day.
Does anyone has the same issue or solution for this?
Hope it makes sense, thank you very much.
Here is the code I am using right now, it almost works, but sometimes I get the wrong date/time (usually it removes 10 hours from date).
export function getCorrectDateAfterDrop(originalDateFrom, originalDateTo, dateFrom) {
const NIGHT_TIME = 11;
dateFrom = moment(dateFrom);
originalDateTo = moment(originalDateTo);
originalDateFrom = moment(originalDateFrom);
let hoursDiff = moment.duration(originalDateTo.diff(originalDateFrom)).asHours();
const sign = Math.sign(hoursDiff);
if (originalDateTo.isAfter(moment(originalDateFrom).hours(20))) {
hoursDiff = (hoursDiff > NIGHT_TIME) ? (hoursDiff - NIGHT_TIME) : hoursDiff;
}
let finalDateToBeChecked = moment(dateFrom).add((hoursDiff * sign), 'hours');
let isDateFromSameAsDateTo = moment(dateFrom).isSame(finalDateToBeChecked, 'day');
if (isDateFromSameAsDateTo && finalDateToBeChecked.hours() < 20) {
// I think the problem is here, but I can't figure it out :D
return finalDateToBeChecked.format();
} else {
const diffUntilShiftEnds = moment.duration(moment(dateFrom).hours(20).diff(dateFrom)).asHours();
hoursDiff -= diffUntilShiftEnds;
const finalDateFrom = moment(dateFrom).add(1, 'days').hours(7);
const finalDateTo = moment(dateFrom).add(1, 'days').hours(7).add(hoursDiff, 'hours');
return getCorrectDateAfterDrop(finalDateFrom, finalDateTo, finalDateFrom);
}
}
Maybe I do not fully understand your question, but I think something like the following should work:
function getCorrectDateAfterDrop(originalDateFrom, originalDateTo, dateFrom) {
return originalDateTo - originalDateFrom + dateFrom;
}
// verify it works:
var origFrom = Date.parse('01 Jan 2018 05:00:00');
var origTo = Date.parse('02 Jan 2018 07:00:00');
var newFrom = Date.parse('02 Jan 2018 01:00:00');
var newTo = getCorrectDateAfterDrop(origFrom, origTo, newFrom)
console.log((Date.parse('03 Jan 2018 03:00:00') === newTo)) // true
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.min.js"></script>

using the time as a dynamic variable to compare values

I am working on programming a page in JS that grabs calendar data from an outside source, imports it into a multidimensional array and uses it to display who is currently working along with their photo, phone number, etc.
Right now I have it set up so that the page reloads every 15 minutes. I'd prefer to have this all done dynamically so that when, say, the clock strikes 5pm the page knows to update without having to wait until the 15 minute refresh is triggered.
All of the work times are pulled from the other calendar in 24 hour format (so 5pm is 1700).
Here's how I'm generating the current time to compare with the start/end times in the calendar:
//Get the current date and time
var dateTime = new Date();
var month = dateTime.getMonth() + 1;
var day = dateTime.getDate();
var dayOfWeek = dateTime.getDay();
var year = dateTime.getYear() + 1900;
//converting hours and minutes to strings to form the 24h time
var hours = dateTime.getHours().toString();
if (hours.length === 1) {
var hours = '0' + hours
};
var minutes = dateTime.getMinutes().toString();
if (minutes.length === 1) {
var minutes = '0' + minutes
};
var time = hours + minutes;
//convert the 24h time into a number to read from later
var timeNumber = parseInt(time);
I then use if statements to compare the start/end times from the imported schedule with timeNumber to determine who is currently working and push that to an array that is eventually displayed on the page with this code:
//figure out who is currently working and put them in the workingNow array
var workingNow = [];
for (i = 0; i < workingToday.length; i++){
//convert time strings to numbers to compare
var startTime = parseInt(workingToday[i][7]);
var endTime = parseInt(workingToday[i][8]);
//compare start and end times with the current time and add those who are working to the new list
if(startTime < timeNumber && timeNumber < endTime){
workingNow.push(workingToday[i]);
}
};
I guess I have just been trying to figure out how to make this comparison of the data in an array with the current time something that is dynamic. Is this possible or would I need to go about this in a completely different way from the ground up?
You should have a look at momentjs. This is a really good library to handle all sort of time and date manipulation.
http://momentjs.com/

Object Oriented JavaScript: How would you go about this?

As I've gotten to know JS better, I've moved from a procedural style to semi-OO (don't ask me what I mean by that: a mess basically!) but now I want to start using it properly. OO appeals to my coding-brain.
However, I'm trying to develop a library of school weeks, and I'm not sure how I'd best go about it.
If I was to simply use an array of weeks, they would look something like this:
WeeksArray[36].StartDate = "2011-09-05";
WeeksArray[36].EndDateSchool = "2011-09-09";
WeeksArray[36].EndDateProper = "2011-09-11";
WeeksArray[36].JSDate = new Date ( 2011, 8, 05 );
WeeksArray[36].Type = "1";
WeeksArray[36].Label = "Week 36: 5th Sept 2011";
Key: Week number according to School Calendar
StartDate / EndDate: MySQL-compatible date ranges
JSDate: JS date object of start of week
Type: school timetable, week 1 or 2
Label: human-readable label indicating start of week
I would like this library to be accessible by other scripts, so that they can load an Array or Object containing all of the weeks in the school calendar. I'd imagine, for instance, one of my scripts producing a drop-down menu from this information, which displays "Week 36: 5th Sept 2011" and when clicked upon sends a request to my PHP script & SQL database then filters the information on screen accordingly. NOTE: I don't need help with the implementation of the latter, it's just an example for context.
I started coding as follows:
var LEAP = {}
LEAP.Schedule = {
init: function() {
this.setWeeks();
}
setWeeks: function() {
var WeeksArray = [];
But the more I look at it, the less correct it feels!
Should I be creating "Week" objects, then a container for them which has a method to return all of the Week objects? I've been reading the OOP chapter in "Pro JavaScript Techniques" by John Resig, but truth be told I don't fully understand it. This feels like the right approach, but an Object within an Object is hurting my head.
The final outcome should be that I include this script on one of my pages, then can use something like var WeeksArray = LEAP.Schedule.getWeeks();, but even then I'm not sure that's realistic?
I'm rather confused...! :D Any help on the subject would be hugely appreciated.
setWeeks: function(){
var WeeksArray = []; //This variable is private, which is probably not your desired result.
}
^ That doesn't work, see comment.
I'd recommend something like this:
External file:
var LEAP = {};
LEAP.Schedule = function(){//init
//all events which should only occur once should be called here
//Create the initial objects, e.g.
this.weeks = [];
this.calculateWeeks();
}
LEAP.Schedule.protoype.calculateWeeks = function(){
for (var i=0; i<52; i++){
this.weeks.push(Math.random()); //For the sake of the example ;)
}
}
LEAP.Schedule.prototype.getWeeks = function(){
return this.weeks;
}
Main file:
var Scheduleobject = new LEAP.Schedule();
var weeks = Scheduleobject.getWeeks();
This feels like a very natural OOP approach, to me.
You can even change the LEAP.Schedule function such that it returns the Weeks array immediately, dependent on the situation.
EDIT
An example of a week class:
LEAP.Schedule.week = function(n_year, n_month, n_day, n_week){
//add code to validate the input
//...
//finished validating, processing:
this.year = n_year;
this.month = n_month;
this.day = n_day;
this.week = n_week;
}
LEAP.Schedule.week.protoype.getStartDate = function(){
return year + "-" + pad(month) + "-" + pad(day);
}
//LEAP.Schedule.week.prototype.*Date are defined in a similar way
//The "EndDateSchool" and "EndDateProper" variables always follow the same pattern. Reduce the number of unnecessary variables by calculating these variables in the prototype function.
LEAP.Schedule.week.prototype.getLabel = function(){
return "week" + this.week + ": " + this.day + (day==1||day==21||day==31?"st":day==2||day==22?"nd":day==3||day==23?"rd":"th") + " " + ["jan", "feb", "mar", "etc"][this.month-1] + " " + this.year;
}
function pad(n){return n>9?n:"0"+n}//a simple function to add a zero for your specific cases.
The week class can be called in this way:
var week = new Schedule.Week(2011, 8, 5, 36); //or this.Week(2011, 8, 5, 36) from the contex of the class.
var startDate = week.getStartDate(); //example`
Following up on my comment.
You might not ever need formal week objects. It might be enough to simply store which weeks are which type, and a formula for converting the number to a date. So your Calendar or Schedule object might have a property indicating the absolute day that week number 1 starts, and an array of week types in order. Then when getWeeks() is called, it can start at week 1 and build an array of the necessary weeks, which could be formal objects, or could simply be associative arrays:
weeks = [];
for (var i = 0; i < this.week_types.length; i++){
weeks[i] = {
"StartDate": this.get_start_date(i),
"JSDate": this.get_js_date(i),
..., //The rest of the properties
"type": this.week_types[i],
"Label": this.get_label(i)
}
Hopefully that starts you on the right track, I'm happy to help if I can provide further clarification.

Categories

Resources