Our blazing fast Grid component built with pure JavaScript


Post by imkookoo »

I'm trying to follow a structure similar to the "drag-from-grid" example, and have it mostly working at the moment, but I'm stuck on one odd issue.

I have a Scheduler Pro and Grid setup like the example. The first time I drag-and-drop an item from the grid into the scheduler, everything looks good and works fine -- the hoverbox is aligned with the mouse cursor.

But afterward, any time I drag an item out of the grid, the hoverbox is pinned to the very top of the browser (the context.newY value in the onSessionDragStart handler is 0). It's aligned well on the X-axis, and if I let go of the mouse cursor on the slot in the scheduler, the event does get moved to that slot.... but of course it looks funny because the box is on the top way outside of the scheduler.

Any idea on what's the issue? Do I have to set any CSS properties of the DIV container that's the parent to the scheduler/slider/grid? Right now it has:

width:100%; 
display: flex; 
flex-direction: row; 
flex: 1 1 100%

Post by alex.l »

Hi imkookoo,

Can you reproduce it with our demo? We will be glad to help you with this problem, but we need a test case to reproduce and debug the problem, as well as a version of the SchedulerPro you used and steps to reproduce.
Please, follow our Support Policy as much as it possible in your case viewtopic.php?f=35&t=772

All best,
Alex

All the best,
Alex


Post by imkookoo »

Thank you for the quick response! I'm doing a proof-of-concept for my company, so I'm using the Scheduler Pro 4.0.3 Trial edition for the moment. The "Drag-from-grid" example that I set up on my web server works fine, so I don't think it's the trial version that's relevant.

I pretty much copy/pasted the sample and altered some small things here and there (below is the code-snippet I'm pretty much using).

Other relevant notes about this example:

  • "this.schedulerConfig" JSON object which is passed to the constructor of SchedulerPro is pasted below the code-snippet
  • "top.viewFrame" in the code-snippet is referencing the HTML frame that the page is in.
  • There's two things that I noticed while debugging this -- unsure if they are related or not:

1) In the "drag" event, context.valid is being successfully set to "true"... but when it gets to the "drop" event, "context.valid" is reverted back to false. I tested setting "context.valid2" to the true in the drag event, and in the drop responding to "valid2" instead, and that sort of works -- the event successfully gets moved over into the schedule for the first time you do it. But as I mentioned, the hoverbox as your dragging stuff from the "Unscheduled Containers" box to the scheduler is stuck at the top. When you let go though, the event does successfully get added to the scheduler where the mouse is.

2) I also noticed a console error saying "context.finalize" method is not found when I drop the event. The event still gets added successfully in the area where the mouse is though.

Please let me know if you require any additional info.

Code-snippet:

this.schedulerPro = new top.viewFrame.bryntum.schedulerpro.SchedulerPro(this.schedulerConfig);
  this.schedulerPro.localeManager.extendLocale("En", this.schedulerLocale);
  this.schedulerPro.calendarManagerStore.add(this.calendarJson);

  class Drag extends top.viewFrame.bryntum.schedulerpro.DragHelper {
    static get defaultConfig() {
        return {
            // Don't drag the actual row element, clone it
            cloneTarget        : true,
            mode               : 'translateXY',
            // Only allow drops on the schedule area
            dropTargetSelector : '.b-timeline-subgrid',
            // Only allow drag of row elements inside on the unplanned grid
            targetSelector     : '.b-grid-row:not(.b-group-row)'
        };
    }

construct(config) {
    super.construct(config);

    this.on({
        dragstart : 'onSessionDragStart',
        drag      : 'onSessionDrag',
        drop      : 'onSessionDrop',
        thisObj   : this
    });
}

onSessionDragStart({ context }) {
    const
        me           = this,
        { schedule } = me,
        mouseX       = context.clientX,
        proxy        = context.element,
        session      = me.grid.getRecordFromElement(context.grabbed),
        newSize      = schedule.timeAxisViewModel.getDistanceForDuration(session.durationMS);

    // save a reference to the session being dragged so we can access it later
    context.session = session;

    // Mutate dragged element (grid row) into an event bar
    proxy.classList.remove('b-grid-row');
    proxy.classList.add('b-sch-event-wrap');
    proxy.classList.add('b-sch-style-border');
    proxy.classList.add('b-unassigned-class');
    proxy.innerHTML = `
        <div class="b-sch-event b-has-content b-sch-event-withicon">
            <div class="b-sch-event-content">
            <div>
                <div>${session.name}</div>
            </div>
        </div>
    `;

    schedule.enableScrollingCloseToEdges(schedule.timeAxisSubGrid);

    // If the new width is narrower than the grabbed element...
    if (context.grabbed.offsetWidth > newSize) {
        const proxyRect = top.viewFrame.bryntum.schedulerpro.Rectangle.from(context.grabbed);

        // If the mouse is off (nearly or) the end, centre the element on the mouse
        if (mouseX > proxyRect.x + newSize - 20) {
            context.newX = context.elementStartX = context.elementX = mouseX - newSize / 2;
            top.viewFrame.bryntum.schedulerpro.DomHelper.setTranslateX(proxy, context.newX);
        }
    }

    proxy.style.width = `${newSize}px`;

    // Prevent tooltips from showing while dragging
    schedule.element.classList.add('b-dragging-event');
}

onSessionDrag({ context }) {
    const
        date                = this.schedule.getDateFromCoordinate(context.newX, 'round', false),
        room                = context.target && this.schedule.resolveResourceRecord(context.target);

    // Don't allow drops everywhere, only allow drops if the drop is on the timeaxis and on top of a room
    if (date && room) {
        context.valid = true;
    }
    else {
        context.valid = false;
    }

    // Save reference to the room so we can use it in onSessionDrop
    context.room = room;
}

// Drop callback after a mouse up, take action and transfer the unplanned session to the real EventStore (if it's valid)
async onSessionDrop({ context }) {
    const
        me                  = this,
        { schedule }        = me,
        { session, target } = context;

    schedule.disableScrollingCloseToEdges(schedule.timeAxisSubGrid);

    // If drop was done in a valid location, set the startDate and transfer the task to the Scheduler event store
    if (context.valid && target) {
        const
            date          = schedule.getDateFromCoordinate(context.newX, 'round', false),
            targetSession = schedule.resolveEventRecord(context.target);

        // Suspending refresh to not have multiple redraws from date change and assignments (will animate weirdly)
        schedule.suspendRefresh();

        // Dropped on a scheduled event, create a dependency between them
        if (date && !targetSession) {
            session.startDate = date;
        }

        // Assigned to the room (resource) it was dropped on
        session.assign(context.room);

        // Commit changes
        await schedule.project.commitAsync();

        // No longer suspending refresh, all operations have finished
        schedule.resumeRefresh();

        // Redraw, wihout transitions to not delay dependency drawing
        schedule.refresh();

        // Finalize the drag operation
        context.finalize();
    }
    // Dropped somewhere undesired, abort
    else {
        me.abort();
    }

    //schedule.element.classList.remove('b-dragging-event');
}
};


// Custom grid that holds unassigned sessions
class UnplannedGrid extends top.viewFrame.bryntum.schedulerpro.Grid {
    static get defaultConfig() {
        return {
            features : {
                stripe : true,
                sort   : 'name'
            },

        columns : [{
            text       : 'Unscheduled Containers',
            flex       : 1,
            field      : 'name',
            htmlEncode : false,
            renderer   : ({ record : session }) => {
                return `${session.name}`;
            }
        }],

        rowHeight : 65,

        disableGridRowModelWarning : true
    };
}

static get $name() {
    return 'UnplannedGrid';
}

set project(project) {
    // Create a chained version of the event store as our store. It will be filtered to only display events that
    // lack assignments
    this.store = project.eventStore.chain(function (eventRecord) {
      return !eventRecord.assignments.length
    });

    // When assignments change, update our chained store to reflect the changes
    project.assignmentStore.on({
        change() {
            this.store.fillFromMaster();
        },
        thisObj : this
    });
}

};


this.schedulerSplitter = new top.viewFrame.bryntum.schedulerpro.Splitter({
    appendTo : 'schedulerDiv'
});

// Holds unplanned sessions, that can be dragged to the schedule
this.unplannedGrid = new UnplannedGrid({
    ref      : 'unplannedGrid',
    appendTo : 'schedulerDiv',
    flex     : '0 1 300px',
    project  : this.schedulerPro.project
});

// Handles dragging
this.schedulerDrag = new Drag({
    grid         : this.unplannedGrid,
    schedule     : this.schedulerPro,
    constrain    : false,
    outerElement : this.unplannedGrid.element
});

this.schedulerConfig contents:

{
   "project":{
      "resourcesData":[
         {
            "id":1,
            "name":"A",
            "calendar":"workhours"
         },
         {
            "id":2,
            "name":"B",
            "calendar":"workhours"
         },
         {
            "id":3,
            "name":"C",
            "calendar":"workhours"
         }
      ],
      "calendar":"workhours",
      "eventStore":{
         "removeUnassignedEvent":false
      }
   },
   "startDate":"2020-11-30T13:00:00.000Z",
   "endDate":"2020-11-30T20:00:00.000Z",
   "ref":"schedulerPro",
   "features":{
      "taskEdit":{
         "editorConfig":{
            "title":"Container"
         }
      },
      "scheduleContextMenu":{
         "items":{
            "addEvent":false
         }
      },
      "dependencies":false,
      "nonWorkingTime":false,
      "resourceNonWorkingTime":true,
      "regionResize":true
   },
   "appendTo":"schedulerDiv",
   "autoHeight":true,
   "rowHeight":50,
   "flex":1,
   "allowOverlap":false,
   "viewPreset":"hourAndDay",
   "columns":[
      {
         "text":"Door",
         "field":"name",
         "width":160
      }
   ],
   "presets":[
      {
         "id":"hourAndDay",
         "name":"Hours",
         "rowHeight":24,
         "tickWidth":70,
         "tickHeight":40,
         "displayDateFormat":"ll LT",
         "shiftUnit":"day",
         "shiftIncrement":1,
         "defaultSpan":24,
         "timeResolution":{
            "unit":"minute"
         },
         "headers":[
            {
               "increment":1,
               "unit":"day",
               "dateFormat":"ddd DD/MM"
            },
            {
               "increment":1,
               "unit":"hour",
               "dateFormat":"LT"
            }
         ],
         "mainHeaderLevel":1,
         "columnLinesFor":1
      }
   ],
   "subGridConfigs":{
      "normal":{
         "flex":1
      }
   }
}

Post by imkookoo »

Here's a screenshot of how it looks like when I'm dragging stuff between grid / scheduler:

screenshot - hoverbox issue.png
screenshot - hoverbox issue.png (46.11 KiB) Viewed 1784 times

Post by pmiklashevich »

Hello,

I tried to copy you code snippets to SchedulerPro/examples/drag-from-grid demo but it worked with no issues. Please submit a full runnable testcase, so we can inspect it. To create one please modify one of our demos and apply minimal changes to replicate your issue. Then zip it up and attach here. How to ask for help is describe here: viewtopic.php?f=35&t=772

Also do not forget to mention steps to reproduce and if you see any error messages please list them here too.

Best,
Pavel

Pavlo Miklashevych
Sr. Frontend Developer


Post by imkookoo »

I have finally been able to figure out what I think is causing the issue. It seems that there's issues if the scheduler panel is inside a parent panel that has position = absolute.

Unfortunately, I don't think I can set the CSS position property to anything else for this panel, as it seems integral to the structure of the site. I could alter the CSS properties of the scheduler panel or the parent panel of that panel, if that would help.

Please advise if there's any workaround.

I took the raw HTML of my site and stripped it very bare to narrow the causes. I've attached this to this message below. To replicate, extract the following archive into a directory, and run the "test.html" in your browser. Steps to replicate issue:

  1. Open "test.html" in a browser
  2. Double-click anywhere in the scheduler to create a new event, specify a name, and save.
  3. Right-click on the event and click "Unassign event", and the event will now move to the unassigned grid.
  4. Drag-and-drop the event back to the scheduler. It will disappear and do nothing.
  5. Do #4 again, and now the hoverbox is pinned to the top.

If you edit the "viewFrame.html", and change the position to "relative" (or use the web dev tools inspector), the steps above work successfully.

example.zip
(5.35 MiB) Downloaded 119 times

Post by mats »

It's caused by poor HTML document sizing. Your HTML/body elements have invalid sizes (height:8px). Fix it by using correct CSS inside your frame:

html,body { height : 100%; }

Post by imkookoo »

That did it! Such a simple fix. Thank you so much and appreciate the quick support -- I will relay that opinion to my company as well so they can use that in pursuing with using your product.

Best regards.


Post Reply