Best way to populate data in DOM with jQuery and Node

I use Node Socket.ioto transfer data from the server to the client browser. On the client, I use jQuery to populate the returned rows in the DOM.

Below is a piece of code that 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 = [];

Prototype Declaration:

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 data received from the 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 features below . Rest is a similar function that populates data in other parts of the 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 is working fine. The problem is that due to the fact that every 10 seconds so many push messages arrive from the socket, the browser cannot process so much data and freezes.

Is there a better way to do this?

+4
4

:

  • , . DOM . Google . - :

    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(''));
        }
    }
    
  • inArray, Array, , , .

    . . , , , , ...

    . , . , Object.create(null), , . - :

    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);
        }
    }
    

    , . . . , , device_id alert_gen_date_time, .

    row.device_id row.alert_gen_date_time. , . , , , , . , "ab" + "cd" "a" + "bcd" "abc" + "d". , : so "ab@cd", "a@bcd", "abc@d".

    , , . , , .

, , , .

+5

, , , , , , .

, , , 10 ... , , , , 10 .

, , . , , , , , , , , 5 ( 15 60 , , , ), , .

, , , .

, pushifnotexists, .

+1

( dom) lookupCache allready

updatedom - , , , .

, . Dom - , , dom , () : D, ...

, , . (var OverSpeedAlerts ..), . : 1. , 2. 3. ( )

, , . ( dom tho), .

var io /*some lib*/, pushServer, alertTypes, alterTypesMapping, $notifications, lookupCache;

//i use the -Length fields to store the "previous" length, so i know if the dom needs updating at all
// and what part is new, no need to re-render perfectly valid html
alertTypes = {
    OverSpeedAlerts: [],
    OverSpeedAlertsLength: 0,
    TripCancellation: [],
    TripCancellationLength: 0,
    GeofenceInOutAlerts: [],
    GeofenceInOutAlertsLength: 0,
    ScheduleOverstay: [],
    ScheduleOverstayLength: 0,
    UnSchduledOverstay: [], //scheduled? sorry ide with spelling check
    UnSchduledOverstayLength: 0,
    SkippedBusStop: [],
    SkippedBusStopLength: 0,
    TripDelayAlert: [],
    TripDelayAlertLength: 0,
    SkippedUnplannedAlert: [],
    SkippedUnplannedAlertLength: 0,
    DelayStartEndAlert: [],
    DelayStartEndAlertLength: 0,
    RouteDeviatedAlert: [],
    RouteDeviatedAlertLength: 0
};

//mapping from types to their lists (some types map to the same list)
alterTypesMapping = {
    'overspeed': 'OverSpeedAlerts',
    'trip_cancellation': 'TripCancellation',
    'Geofence-In': 'GeofenceInOutAlerts',
    'Geofence-Out': 'GeofenceInOutAlerts',
    'Scheduled-Overstay': 'ScheduleOverstay',
    'Unscheduled-Overstay': 'UnSchduledOverstay',
    'Skipped Unplanned': 'SkippedBusStop',
    'Delay Start': 'TripDelayAlert',
    'Delay End': 'TripDelayAlert',
    'Route Deviated': 'RouteDeviatedAlert',
    'Multiple Bus Entry': 'MultipleBusEntry'
};

//cache dom lookup
$notifications = $('#notifications');

//we serialize the relevant message parts into an unique id, used for de-duping
//<id> => <alert_type>|<device_id>|<alert_gen_date_time>
lookupCache = {};

function process_data (data) {
    var i, l, rows, id;
    rows = data.message;
    l = rows.length;

    //update dom row count
    $notification.html(l);

    for (i=0; i<l; ++i) {    //caching length in l, ++i is faster than i++
        id = rows[i].alert_type + '|' + rows[i].device_id + '|' + rows[i].alert_gen_date_time;
        if (!lookupCache[id]) {
            lookupCache[id] = 1;    //set it to truthy so next time around its there
            //not in cache push it to the relevant list
            //you used unshift here, that essentially moving all other elements in the list one spot and
            //adding the new one at index 0 (speed O(n) eg increases with more elements in the list)
            //instead you can push the new element to the end, (speed O(1) constant speed)
            // and when iterating the list doing that in reverse
            alertTypes[alterTypesMapping[rows[i].alert_type]].push(rows[i]);
        }
    }
    updateDom();
}

function updateDom () {
    var keys, i, l, html;

    //here we check all length fields in the alertTypes and see if the actual list length
    //is greater than their -Length
    //if so we update the relevant dom

    keys = Object.keys(alertTypes);
    for (i=0, l=keys.length; i<l; ++i) {
        //skip the -Length keys
        if (keys[i].match('Length')) {
            continue;
        }
        //please add a data-type="<type>" to the a's, so much better to lookup by attribute instead of text matching content
        $('#tabs ul a[data-type="' + keys[i] + '"]').html(keys[i] + '(' + alertTypes[keys[i] + 'Length'] + ')');

        //since well only update the dom, i presume at this point there is a dom with the table with headers
        //(use thead and th for this please)
        //(and use tbody for a table body)
        //now we iterate the new elements (from list.length back to key-Length)
        //j starts at the length of the list, and ends at m, the previous length
        //counting backwards
        html = [];
        for (j=alertTypes[keys[i]].length, m=alertTypes[keys[i] + 'Length']; j>m; --j) {
            //array join is almost always faster than string concatenation
            //since strings are not mutable in js (eg. you create a new string every +)
            html.push([
                '<tr>',
                    '<td>',
                        alertTypes[keys[i]].depot_name,
                    '</td>',
                    '<td>',
                        alertTypes[keys[i]].route_name,
                    '</td>',
                    '<td>',
                        alertTypes[keys[i]].schedule_no,
                    '</td>',
                    '<td>',
                        alertTypes[keys[i]].trip_number,
                    '</td>',
                    '<td>',
                        alertTypes[keys[i]].trip_direction,
                    '</td>',
                    '<td>',
                        alertTypes[keys[i]].alert_sub,
                    '</td>',
                    '<td ',
                       'title="',
                            ConvertToValidTooltip(alertTypes[keys[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>'
            ].join(''));
        }
        //and finally we update the key-Length so next time well only add what is newer than what we are about to add
        alertTypes[kesy[i] + 'Length'] = alertTypes[keys[i]].length;
        //get the dom element we have to update
        $('#' + keys[i] + ' tbody').prepend(html.join(''));
    }
}

if (io !== undefined) {   //no need for typeof when checking undefined, check undefined directly with equality (eg. undefined === undefined)
    pushServer = io.connect('http://SomeIP:3000');
    pushServer.on('entrance', process_data);
}
+1

- DOM.

, DOM , . , DOM.

:

function CreateOverSpeedGrid() {
  // Use an ID if possible. It the fastest way to select DOM nodes. After you can use css classes, or dom custom attributes (data-***). Using contains is very slow.
  $('#overspeed_alerts').html('OverSpeed Alerts(' +  OverSpeedAlerts.length + ')');
  if (OverSpeedAlerts.length != 0) {
      // Fast HTML string: using an array is better than concatenating multiple strings.
      var content = ['<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>'];
      for (var i = 0; i < OverSpeedAlerts.length; i++) {
        // optimize by accessing your item in array only once.
        var alert = OverSpeedAlerts[i];
        content.push('<tr> <td>', 
            alert.depot_name, 
            '</td> <td>', 
            alert.route_name,
            '</td> <td>',
            alert.schedule_no,
            '</td> <td>',
            alert.trip_number,
            '</td> <td>,
            alert.trip_direction,
            '</td><td> ',
            alert.alert_sub,
            '</td><td title="',
            ConvertToValidTooltip(alert.alert_msg),
            '" style="text-decoration:underline;cursor:pointer;">Place mouse pointer to view message </td><td>',
            new Date(alert.alert_gen_date_time * 1000),
            '</td> </tr>'];
      }
      // Do not select multplie time your table node: it better to store it in a variable.
      $('#notifyOverspeed table').html(content.join(''));
   }
}

DOM- N + 4 (N - OverSpeedAlerts.

pushIfNotExist, , . -, "alert_gen_date_time" "device_id":

// use hash in addition to your array
OverSpeedAlerts = [];
var existingOverSpeedAlerts = {};

for (var i = 0; i < rows.length; i++) {
   // optimize access to row
   var row = rows[i];
   if (row.alert_type == 'overspeed') {
       // join device_id and alert_gen_date_time
       var key = row.device_id + '_' + row.alert_gen_date_time;
       // existence check ! very efficient.
       if (!(key in existingOverSpeedAlerts )) {
          // does not exist yet: adds it, and update your hash.
          existingOverSpeedAlerts[key] = row;
          OverSpeedAlerts.push(row);
       }
    }
    ...

, , . inArray pushIfNotExist !

You can also decompose your code by dynamically selecting a hash, depending on the type of alert_type. This will not make your code faster, but more readable (which is also valuable!)

Sort of:

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);

        var alerts = {}; 
        var keys = {};

        // reuse NumberOfRows  here
        for (var i = 0; i < NumberOfRows ; i++) {
          // optimize access to row
          var row = rows[i];
          var type = row.alert_type;

          // add here specificities relative type aggregation
          if (type === 'Geofence-In' || type === 'Geofence-Out') {
            type = 'Geofence-Inout';
          } else if (type === 'Skipped Unplanned' || type === 'Skipped-Busstop') {
            type = 'SkippedBusStop';
          } else if (type === 'Delay Start' || type === 'Delay End') {
            type = 'TripDelayAlert';
          }

          // first alert of a kind !
          if (!(type in alerts)) {
            // init your alert specific array and key hash
            alerts[row.alert_type] = [];
            keys[row.alert_type] = {};
          }

          // join device_id and alert_gen_date_time
          var key = row.device_id + '_' + row.alert_gen_date_time;

          // existence check ! very efficient.
          if (!(key in keys[row.alert_type])) {
            // does not exist yet: adds it, and update your hash.
            keys[row.alert_type][key] = row;
            alerts[row.alert_type].push(row);
          }
        }

        // And now displayal
        DisplayAlerts(alerts)
     }
...
function DisplayAlerts(alerts) {
  for (var key in alerts) {
     var array = alerts[key];

     // My hypothesis is that rendering of a given alert type is inside a node with the kind as css ID.
     $('#'+key+' .caption').html('Alerts(' +  array.length + ')');
     if (array.length != 0) {
       // Fast HTML string: using an array is better than concatenating multiple strings.
       var content = ['<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>'];
       for (var i = 0; i < OverSpeedAlerts.length; i++) {
         // optimize by accessing your item in array only once.
         var alert = array[i];
         content.push('<tr> <td>', 
            alert.depot_name, 
            '</td> <td>', 
            alert.route_name,
            '</td> <td>',
            alert.schedule_no,
            '</td> <td>',
            alert.trip_number,
            '</td> <td>,
            alert.trip_direction,
            '</td><td> ',
            alert.alert_sub,
            '</td><td title="',
            ConvertToValidTooltip(alert.alert_msg),
            '" style="text-decoration:underline;cursor:pointer;">Place mouse pointer to view message </td><td>',
            new Date(alert.alert_gen_date_time * 1000),
            '</td> </tr>'];
        }
        // Do not select multplie time your table node: it better to store it in a variable.
        $('#' + key + ' table').html(content.join(''));
     }
  }

Happy coding!

+1
source

Source: https://habr.com/ru/post/1523296/


All Articles