Tuesday, December 16, 2014

To Infinite Scrolling and Beyond!

Introduction

Earlier this year I worked on a pure .Net project that used a lot of JavaScript and jQuery. One of the challenges was displaying some of the data in a modal popup window. At first, we simply loaded all of the records and displayed them. Then we got really creative and implemented infinite scrolling which resulted in a much better user experience. This blog post is all about the implementation, how it works and how it’s coded.

The Problem

As you can see from the screen shot below, there’s 4,291 records that could be displayed on this popup screen.  The issue with this approach was the speed, or lack there of, that the screen loaded.  Sometimes it would take over 10 seconds to load.  This is not an acceptable solution for this screen.  One of the best and most common solutions for this situation is to implement infinite scrolling on the popup screen.  This simply means that when the screen first appears, there are only 100 records loaded.  The user can then trigger the next 100 records to load by moving the scrollbar indicator to the bottom of the scrollbar.  Behind the scenes, this triggered a REST API call which returned the next set of data.  The new set of data was then appended to the existing data. 

image

 

The Code

The code breaks down into client and server side. 

Client Side Code


$("#jobsList").scroll(function () {
        var elem = $('#jobsList');
        if (elem[0].scrollHeight - elem.scrollTop() == elem.outerHeight()) {
            scrollingObject.PageNumber++;
            if (scrollingObject.PageNumber <= scrollingObject.TotalPages) {
                if (verbose) console.log('scroll');
                getNextJobList();

            }
        }
    });


The snippet above adds a scroll event to the popup window.  The event checks the position of the scrollbar indicator.  When it’s at the bottom, the getNextJobList function is called.

GetNextJobList function

The bulk of the getNextJobList function simply calls the REST Service and appends the returned data to the existing data.  In the REST call, I used an public object to pass in the page number, sort column, softField and sort order values.  Those objects are used throughout the application and defined as follows

var scrollingObject = { PageNumber: 1, TotalPages:0 };
var sortingObject = { SortField: "name", SortOrder: "asc" };

 

function getNextJobList()
{
…..

$.ajax({
      type: 'GET',
      dataType: 'json',
      url: appRoot + '/api/jobs' + buildCriteriaQueryString('?') + "&page=" + scrollingObject.PageNumber + "&sortColumn=" + sortingObject.SortField + "&sortOrder=" + sortingObject.SortOrder,
      data: {},
      success: function (data) {
          //append the data to the existing data.
       },
      error: function (a, b, c) {
          // handle the exception.

},
    ….

Server Side Code

This is the method called from the client side.  Basically, it queries the database and returns the next set of jobs.

public IEnumerable<JobModel> GetJobs(string g = "1", string f = "1", string exp = "1", string cs = "", int page = 1, string sortColumn = "", string sortOrder = "ASC")
      {
          bool asc = true;
          if (sortOrder != null)
          {
              asc = sortOrder.ToLower() == "asc";
          }
          if (sortColumn == null)
          {
              sortColumn = "name";
          }
          IQueryable<JobDTO> jobsRaw = base.UnitOfWork.JobRepository.FilteredJobs(g, f, cs, exp);
          List<JobDTO> jobEntities = asc ? jobsRaw.OrderBy(GetLambda(sortColumn)).ThenBy(j => j.Name).ToPagedList(page, 100).ToList() : jobsRaw.OrderByDescending(GetLambda(sortColumn)).ThenBy(j => j.Name).ToPagedList(page, 100).ToList(); //base.UnitOfWork.JobRepository.FilteredJobs(g, f, cs, exp).OrderBy(j => j.Name).ToPagedList(page, 100).ToList();
          List<JobModel> jobModels = new List<JobModel>();
          JobModel job;
          foreach (JobDTO entity in jobEntities)
          {
              job = TheModelFactory.Create(entity);
              jobModels.Add(job);
          }

          return jobModels;
      }

 

The Result

The result of not loading all the records in the popup window at the same time were faster load times, a better user experience and a higher quality software solution for our client. This project was a lot of fun to work on and the solution was pretty easy to implement.