To see the effect after sorting:

The problem background

Recently, I used the fullcalendar plug-in for my company business. The sorting function cannot meet business requirements. The order derived from “eventOrder” in the fullcalendar document does not meet expectations in some scenarios. For example, all-sky events have mixed sky events. In the sorting process, there will be “fill” phenomenon. Results in not being sorted in the given order.

Fullcalendar version: v4.4.0

The source code interpretation

// Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels.
        // NOTE: modifies segs
        DayGridEventRenderer.prototype.buildSegLevels = function (segs) {
          var isRtl = this.context.isRtl;
          var colCnt = this.dayGrid.colCnt;
          var levels = [];
          var i;
          var seg;
          var j;
          // Give preference to elements with certain criteria, so they have
          // a chance to be closer to the top.
1         segs = this.sortEventSegs(segs);
          for (i = 0; i < segs.length; i++) {
            seg = segs[i];

            // loop through levels, starting with the topmost, until the segment doesn`t collide with other segments
            for (j = 0; j < levels.length; j++) {
2             if(! isDaySegCollision(seg, levels[j])) {break;
              }
            }
            // `j` now holds the desired subrow index
            seg.level = j;
            seg.leftCol = isRtl ? (colCnt - 1 - seg.lastCol) : seg.firstCol; // for sorting only
            seg.rightCol = isRtl ? (colCnt - 1 - seg.firstCol) : seg.lastCol; // for sorting only
            (levels[j] || (levels[j] = [])).push(seg);
          }
          // order segments left-to-right. very important if calendar is RTL
          for (j = 0; j < levels.length; j++) {
3           levels[j].sort(compareDaySegCols);
          }
          return levels;
        };
Copy the code

This is the fullcalendar month view sort core source. He is the default “line by line” algorithm. Of course, this “row by row padding” is my personal interpretation of the nickname. The so-called “line-by-line” is the grouping unit of fullcalendar sorted processing before rendering events. Is rendered one line per week. Instead of one TD cell per day as we normally understand it.

Levels are a collection of two-dimensional arrays of events displayed hierarchically in a row, or a week.

In line 1 of the source code, the data segs gets is actually an array of the “eventOrder” configuration exposed to us by the plug-in.

But in fact the “isDaySegCollision” function at line 2 will determine if the current event traversed conflicts with the sorted current path.

// Computes whether two segments columns collide. They are assumed to be in the same row.
    function isDaySegCollision(seg, otherSegs) {
        var i;
        var otherSeg;
        for (i = 0; i < otherSegs.length; i++) {
            otherSeg = otherSegs[i];
            if (otherSeg.firstCol <= seg.lastCol &&
                otherSeg.lastCol >= seg.firstCol) {
                return true; }}return false;
    }
Copy the code

If there is no conflict, as shown in the figure, the event “sort 3” will complement to the first layer (levels[0]), ignoring the sort. This is also considered to make full use of the exhibition space. If this kind of “filling” really does not fit the business scenario. Add a judgment to the if condition at identifier 2 to prevent the “fill” action.

An implementation method

The first thing to do when traversing the event SEGS is to record the following:

for(i = 0; i < segs.length; i++) { ... somethingfor (z = seg.firstCol; z <= seg.lastCol; z++) {
      cells[seg.row + The '-'+ z] = seg.level; }}Copy the code

Cells is based on cell TD and records the event-level value of the current cell.

If judgment at Mark 2:

if(! isDaySegCollision(seg, levels[j]) && j >= (cells[seg.row +The '-' + seg.firstCol] || 0)) {
    break;
  }
Copy the code

The principle is whether the current event traversed conflicts with the ranked levels[j]. If there is no conflict and the current level j is greater than or equal to the maximum level value recorded for the current cell, break is executed; Instead of increasing the level j, insert the current event into the levels[j] queue.

The complete code is as follows:

DayGridEventRenderer.prototype.buildSegLevels = function(segs) { var isRtl = this.context.isRtl; var colCnt = this.dayGrid.colCnt; var levels = []; var i; var seg; var j; var z; Var cells = {}; // Give preference to elements with certain criteria, so they have // a chance to be closer to the top. segs = this.sortEventSegs(segs);for (i = 0; i < segs.length; i++) {
          seg = segs[i];

          // loop through levels, starting with the topmost, until the segment doesn`t collide with other segments
          for(j = 0; j < levels.length; J ++) {// Add j > (cells[')${seg.row}-${seg.firstCol}`] | | 0)if(! isDaySegCollision(seg, levels[j]) && j >= (cells[`${seg.row}-${seg.firstCol}`] || 0)) {
                  // console.log('cells2', cells, j, seg.eventRange.def.extendedProps.orderNum, seg);
                  break;
              }
          }
          // `j` now holds the desired subrow index
          seg.level = j;
          seg.leftCol = isRtl ? (colCnt - 1 - seg.lastCol) : seg.firstCol; // for sorting only
          seg.rightCol = isRtl ? (colCnt - 1 - seg.firstCol) : seg.lastCol; // forsorting only (levels[j] || (levels[j] = [])).push(seg); // Add a new recordfor (z = seg.firstCol; z <= seg.lastCol; z++) {
              cells[`${seg.row}-${z}`] = seg.level;
          }
      }
      // order segments left-to-right. very important if calendar is RTL
      for (j = 0; j < levels.length; j++) {
          levels[j].sort(compareDaySegCols);
      }
      return levels;
   }
Copy the code