Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to populate data on DOM using jQuery and Node

I am using Node's Socket.io to push data from server to client browser. On client I use jQuery to populate the returned rows in the DOM.

Below the code snippet I use to populate the data returned by Socket.io.

var OverSpeedAlerts = [];
var TripCancellation = [];
var GeofenceInOutAlerts = [];
var ScheduleOverstay = [];
var UnSchduledOverstay = [];
var SkippedBusStop = [];
var TripDelayAlert = [];

var SkippedUnplannedAlert = [];
var DelayStartEndAlert = [];
var RouteDeviatedAlert = [];

var MultipleBusEntry = [];

Declaring the prototype:

Array.prototype.inArray = function (comparer) {
    for (var i = 0; i < this.length; i++) {
        if (comparer(this[i])) return true;
    }
    return false;
};

// adds an element to the array if it does not already exist using a comparer 
// function
Array.prototype.pushIfNotExist = function (element, comparer) {
    if (!this.inArray(comparer)) {
        this.unshift(element);
    }
};

Processing the data recieved from socket:

if (typeof io !== 'undefined') {
    var pushServer = io.connect('http://SomeIP:3000');
    pushServer.on('entrance', function (data) {
        var rows = data.message;
        var NumberOfRows = rows.length;
        $('#notifications').html(NumberOfRows);
        // console.log(rows);
        OverSpeedAlerts = [];
        TripCancellation = [];
        GeofenceInOutAlerts = [];
        ScheduleOverstay = [];
        UnSchduledOverstay = [];
        SkippedBusStop = [];
        TripDelayAlert = [];

        SkippedUnplannedAlert = [];
        DelayStartEndAlert = [];
        RouteDeviatedAlert = [];

        var MultipleBusEntry = [];
        for (var i = 0; i < rows.length; i++) {
            if (rows[i].alert_type == 'overspeed') {
                OverSpeedAlerts.pushIfNotExist(rows[i], function (e) {
                    return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
                });
            }
            else if (rows[i].alert_type == 'trip_cancellation') {
                TripCancellation.pushIfNotExist(rows[i], function (e) {
                    return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
                });
            }
            else if (rows[i].alert_type == 'Geofence-In' || rows[i].alert_type === 'Geofence-Out') {
                GeofenceInOutAlerts.pushIfNotExist(rows[i], function (e) {
                    return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
                });
            }
            else if (rows[i].alert_type == 'Scheduled-Overstay') {
                ScheduleOverstay.pushIfNotExist(rows[i], function (e) {
                    return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
                });
            }
            else if (rows[i].alert_type == 'Unscheduled-Overstay') {
                UnSchduledOverstay.pushIfNotExist(rows[i], function (e) {
                    return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
                });
            }
            else if (rows[i].alert_type == 'Skipped Unplanned' || rows[i].alert_type == 'Skipped-Busstop') {
                SkippedBusStop.pushIfNotExist(rows[i], function (e) {
                    return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
                });
            }
            else if (rows[i].alert_type == 'Delay Start' || rows[i].alert_type == 'Delay End') {
                TripDelayAlert.pushIfNotExist(rows[i], function (e) {
                    return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
                });
            }
            else if (rows[i].alert_type == 'Route Deviated') {
                RouteDeviatedAlert.pushIfNotExist(rows[i], function (e) {
                    return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
                });
            }
            else if (rows[i].alert_type == 'Multiple Bus Entry') {
                MultipleBusEntry.pushIfNotExist(rows[i], function (e) {
                    return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
                });
            }

        }
        CreateOverSpeedGrid();
        CreateTripCancellation();
        CreateGeofenceGrid();
        CreateScheduleOverstayGrid();
        CreateUnSchduledOverstayGrid();
        CreateTripDelayGrid();
        CreateSkippedBusStop();
        CreateRouteDeviationAlert();
        CreateMultipleBusEntry();
    });
    pushServer.on('end', function (socket) {

    });
}

One of the above functions are as below. Rest are the similar function filling data in other parts of DOM.

function CreateOverSpeedGrid() {
    $('#tabs ul').find('a:contains("Overspeed Alerts")').html('OverSpeed Alerts(' + OverSpeedAlerts.length + ')');
    if (OverSpeedAlerts.length != 0) {
        $('#notifyOverspeed table').html('');
        $('#notifyOverspeed table').append('<tr class="ui-widget-header"> <th> Depot </th> <th> Route </th> <th> Schedule </th> <th> Trip Number </th><th>Trip Direction</th> <th> Alert Summary</th> <th> Alert Details </th> <th> Generated On </th> </tr>'); //new Date([UNIX Timestamp] * 1000);
        for (var i = 0; i < OverSpeedAlerts.length; i++) {
            $('#notifyOverspeed table').append('<tr> <td>' + OverSpeedAlerts[i].depot_name + '</td> <td>' + OverSpeedAlerts[i].route_name + '</td> <td>' + OverSpeedAlerts[i].schedule_no + '</td> <td>' + OverSpeedAlerts[i].trip_number + ' </td> <td>' + OverSpeedAlerts[i].trip_direction + '</td><td> ' + OverSpeedAlerts[i].alert_sub + ' </td> <td title="' + ConvertToValidTooltip(OverSpeedAlerts[i].alert_msg) + '" style="text-decoration:underline;cursor:pointer;"> ' + "Place mouse pointer to view message" + ' </td> <td>' + new Date(OverSpeedAlerts[i].alert_gen_date_time * 1000) + ' </td> </tr>');
        }
    }
}

The above code works fine. BUt the problem is that due to so many push messages being received every 10 seconds from socket the browser is not able to process so much data and hangs up.

is there any better way to do it??

like image 776
writeToBhuwan Avatar asked Feb 15 '23 00:02

writeToBhuwan


2 Answers

I see the following problems in this code:

  1. You update your table by manipulating the document multiple times. It is better for performance to update the DOM in one operation. There's a Google article about this. So something like:

    function CreateOverSpeedGrid() {
        $('#tabs ul').find('a:contains("Overspeed Alerts")').html('OverSpeed Alerts(' + OverSpeedAlerts.length + ')');
        if (OverSpeedAlerts.length != 0) {
            var html = [];
            html.push('<tr class="ui-widget-header"> <th> Depot </th> <th> Route </th> <th> Schedule </th> <th> Trip Number </th><th>Trip Direction</th> <th> Alert Summary</th> <th> Alert Details </th> <th> Generated On </th> </tr>'); //new Date([UNIX Timestamp] * 1000);
            for (var i = 0; i < OverSpeedAlerts.length; i++) {
                html.push('<tr> <td>' + OverSpeedAlerts[i].depot_name + '</td> <td>' + OverSpeedAlerts[i].route_name + '</td> <td>' + OverSpeedAlerts[i].schedule_no + '</td> <td>' + OverSpeedAlerts[i].trip_number + ' </td> <td>' + OverSpeedAlerts[i].trip_direction + '</td><td> ' + OverSpeedAlerts[i].alert_sub + ' </td> <td title="' + ConvertToValidTooltip(OverSpeedAlerts[i].alert_msg) + '" style="text-decoration:underline;cursor:pointer;"> ' + "Place mouse pointer to view message" + ' </td> <td>' + new Date(OverSpeedAlerts[i].alert_gen_date_time * 1000) + ' </td> </tr>');
            }
            // Change the rows in one operation.
            $('#notifyOverspeed table').html(html.join(''));
        }
    }
    
  2. The inArray method you add to Array has to scan the whole array before determining that an element is not already in the array.

    Ideally, this filtering would be done at the sending end. This would be best. However, maybe you are using third party data that cannot be filtered at the source, so...

    There is a way to do better. If order is important, you could still use arrays to store your objects. Then you could use an object created with Object.create(null) to serve as an associative array only to record whether you've seen an object or not. So something like:

    var OverSpeedAlerts = [];
    var OverSpeedAlertsSeen = Object.create(null);
    
    for (var i = 0; i < rows.length; i++) {
        var row = rows[i];
        var key = row.device_id + row.alert_gen_date_time;
    
        if (row.alert_type == 'overspeed' && !OverSpeedAlertsSeen[key]) {
            OverSpeedAlertsSeen[key] = true;
            OverSpeedAlerts.push(row);
        }
    }
    

    I've used this method more than once but the code above is not tested. Typos or brainos may lurk in there. In this example the key is computed once for all types of alerts. Looking at your code it looked like all your alerts were compared on device_id and alert_gen_date_time so generating the key once for each item in the array is correct.

    How to generate they key depends on the possible values of row.device_id and row.alert_gen_date_time. You might need a separator to avoid possible ambiguities. For instance, in a case I worked on, I had to combine values where all the letters of the alphabet were valid. So without a separator there would be no way to distinguish "ab" + "cd" from "a" + "bcd" from "abc" + "d". I used a separator that could not appear in the values to build the key: so "ab@cd", "a@bcd", "abc@d".

    It would also be possible to use an associative array of associative arrays to do the check but I'm not convinced that it would yield a major speed improvement. It would certainly make the code more complex.

I can think of other changes that could be made to improve speed but I don't think they could provide substantial gains.

like image 192
Louis Avatar answered Feb 17 '23 01:02

Louis


Not knowing the details of your app, I'm going to work with the assumption that you need all of that data in the same interface, rather than being able to split it up between, I dunno, tabs or pages or what have you.

If you find it's too much data, presumably your messages don't cycle through so quickly that every 10 seconds it's a whole new batch of data... you likely have longer lived messages sitting in there, being deleted and re-created every 10 seconds.

If you instead changed it so that each update contained only changes, e.g. a new total per list, and then rows to be added and rows to be deleted, you'd likely dramatically improve your performance, and then you could run a full update every, i dunno, 5 minutes (or 15 minutes, or 60 minutes, whatever you feel your app can tolerate) just to make sure you don't get out of sync.

This is almost exactly one of the methods used in video compression, just record the changes from frame to frame, then every so often use a keyframe for error correction.

If you do this, you could eliminate your pushifnotexists step, just loop directly through your response data and update the table all in the same step.

like image 33
Jason Avatar answered Feb 17 '23 03:02

Jason