Our pure JavaScript Scheduler component


Post by Dev@2609 »

Hi Team,

I want to autoshift the tasks when 1 task is dropped on other. I referred the drag from grid demo and tried below code but does not seem to work.

The rescheduleoverlapping tasks seems to have some issue

Any assistance would be helpful
import { Component, ViewChild, ElementRef, AfterViewInit, OnInit, Input } from '@angular/core';
import { SchedulerComponent } from 'bryntum-angular-shared';
import { HttpClient } from '@angular/common/http';
// UMD bundle is used to support IE11 browser. If you don't need it just use import from 'bryntum-scheduler' instead
import { DateField, DomClassList, DateHelper, DragHelper, Scheduler, RowReorder, DependencyModel, StateTrackingManager, Store, ColumnReorder, Grid, GridFeatures, PresetManager, EventResize, Toast, DomHelper, Rectangle, WidgetHelper, EventModel, EventStore, Splitter } from 'bryntum-scheduler/scheduler.umd.js';
import { SCHEDULER } from '@angular/core/src/render3/component_ref';
//import { Scheduler } from 'rxjs';

declare var window: any;
let a, b, schedule;
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements AfterViewInit, OnInit {

  @ViewChild(SchedulerComponent) scheduler: SchedulerComponent;
  @ViewChild('datePickerContainer') datePickerContainer: ElementRef;
  @ViewChild('Scheduler') schedule: Scheduler;

  // rowReorderFeature = true;
  @Input() autoRescheduleTasks: boolean = true;

  dependenciesFeature = true;
  scheduleTooltipFeature = false;


  filterBarFeature = true;
  multiEventSelect = true;
  // eventDragCreateFeature = true;
  //stripeFeature = true;
  // bind properties from the application to the scheduler component
  rowHeight = 50;

  crudManager = {
    autoLoad: true,
    autoCommit: true,
    eventStore: {
      modelClass: EventModel,
      // durationUnit : 'h'
    },
    transport: {

      load: {
        url: 'assets/data/data.json',

      },
      // sync: {
      // method: 'PUT',

      // url: 'https://localhost:3000/events',


      // },

    },

    // autoSyncTimeout : 100,
    // autoSync: true,


  }

  barMargin = 8;

  startDate = new Date(2018, 1, 7, 8);
  endDate = new Date(2019, 2, 7, 22);
  // viewPreset = 'weekAndDayLetter';
  viewPreset = 'weekAndDay';
  listeners = {
    eventselectionchange() {
      const count = this.Scheduler.schedulerEngine.selectedEvents.length;
      Toast.show(count + " " + "selected");
      //countLabel.html = `${count} test${count === 1 ? '' : 's'} selected`;
    }
  }
  columns = [
    {
      type: 'resourceInfo', text: 'Resource', field: 'name',

      cellMenuItems: [
        {
          text: 'Lock',
          icon: 'b-fa b-fa-fw b-fa-lock',
          onItem: ({ record, eventRecord }) => {

            record.flagged = true;
            Toast.show('you have locked' + " " + record.name);
            eventRecord.draggable = false;
            eventRecord.resizable = false;
          }
        },
        {
          text: 'Unlock',
          icon: 'b-fa b-fa-fw b-fa-unlock',
          onItem: ({ record, eventRecord }) => {
            Toast.show('you have unlocked' + " " + record.name);
            record.flagged = false;
            eventRecord.draggable = true;
            eventRecord.resizable = true;

          }
        }
      ],
      imagePath: 'assets/users/'
    },
    {
      text: 'lossrate',
      field: 'loss'
    },

  ];

  nonWorkingTimeFeature = {

    highlightWeekends: true

  };

  rowReorderFeature = {

    listeners: {
      gridRowBeforeDragStart: function (event) {
        const context = event.context,
          record = this.grid.getRecordFromElement(context.element);

        return !record.flagged;
      }
    }
  }



  eventContextMenuFeature = {

    // Extra items for all events
    items: {
      deleteEvent: false,
      eventEdit: false,
      lockitem:
      {

        text: 'Lock',
        icon: 'b-fa b-fa-fw b-fa-lock',
        onItem({ eventRecord }) {

          eventRecord.flagged = true;
          eventRecord.draggable = false;
          eventRecord.resizable = false;
          a = eventRecord.data.color;
          eventRecord.style = 'color:black;background-color:gray';
          Toast.show('you have locked' + " " + eventRecord.name);

        }
      },

      unlockitem:

      {
        text: 'unLock',
        icon: 'b-fa b-fa-fw b-fa-unlock',
        onItem({ eventRecord }) {

          eventRecord.draggable = true;
          eventRecord.resizable = true;
          eventRecord.style = a;
          Toast.show('you have unlocked' + " " + eventRecord.name);

        }

      }

    }
  };


  eventRenderer = ({ eventRecord, resourceRecord, tplData }) => {
    return `
        <div class="info">
            <div class="name">${eventRecord.name}</div>
            <div class="desc">${eventRecord.desc}</div>
        </div>
      `;

  };



  ngOnInit() {

    //



  }
  ngAfterViewInit() {
    // exposing scheduling engine to be easily accessible from console


    const
      scheduler = this.scheduler.schedulerEngine,
      eventStore = scheduler.eventStore
      ;


    window.scheduler = this.scheduler.schedulerEngine;


    eventStore.on({
      update: ({ record, changes }) => {
        if ('resourceId' in changes && !record.resourceId) {
          eventStore.remove(record);
          //  grid.store.add(record);
        }
        if (this.autoRescheduleTasks) {
          this.rescheduleOverlappingTasks(record, eventStore);
          // eventStore.beginBatch();
        }
      },
      add: ({ records }) => {

        if (this.autoRescheduleTasks) {
          eventStore.beginBatch();
        }

        if (this.autoRescheduleTasks) {
          records.forEach((eventRecord) => this.rescheduleOverlappingTasks(eventRecord, eventStore));
          eventStore.endBatch();
        }
      }
    });
    this.initDrag(scheduler);

    // access the engine to reach the entire scheduler API, see API docs at /docs.
    // some small examples:
    //
    // 1. Sort resources by name
    // this.scheduler.schedulerEngine.resourceStore.sort('name');
    //
    // 2. Add a new event
    // this.scheduler.schedulerEngine.eventStore.add({ startDate : xx, endDate: xx, ... });
    //
    // 3. Change the color of the first event
    // this.scheduler.schedulerEngine.eventStore.first.eventColor = 'orange';

  }
  //  
  // this.schedule.features.rowReorder.disabled = false;


  // fetch data for the scheduler
  onToggle(pressed) {
    console.log('hit')
    this.autoRescheduleTasks = pressed;

  } // eo function onRescheduleClick


  initDrag(scheduler) {
    const drag = new DragHelper({
      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',
      constrain: false,
      // outerElement : grid.element
    });

    drag.on({
      dragstart: ({ event, context }) => {
        const
          me = drag,
          mouseX = context.clientX,
          proxy = context.element
          //  task       = grid.getRecordFromElement(context.grabbed),
          // newWidth   = scheduler.timeAxisViewModel.getDistanceForDuration(task.durationMS)
          ;

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

        // 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-unassigned-class');
        // proxy.innerHTML = task.name;

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

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

        // proxy.style.width = `${newWidth}px`;

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

      }, // eo dragstart listener
      drag: ({ event, context }) => {
        const
          me = drag,
          date = scheduler.getDateFromCoordinate(DomHelper.getTranslateX(context.element), 'round', false),
          resource = context.target && scheduler.resolveResourceRecord(context.target)
          ;

        // Don't allow drops anywhere, only allow drops if the drop is on the time axis and on top of a Resource
        context.valid = context.valid && Boolean(date && resource);

        // Save reference to resource so we can use it in onTaskDrop
        context.resource = resource;
      }, // eo drag listener
      drop: ({ context, event }) => {
        const
          me = drag,
          task = context.task,
          target = context.target
          ;

        // 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 = scheduler.getDateFromCoordinate(DomHelper.getTranslateX(context.element), 'round', false),
            // Try resolving event record from target element, to determine if drop was on another event
            targetEventRecord = scheduler.resolveEventRecord(context.target);

          if (date) {
            // Remove from grid first so that the data change
            // below does not fire events into the grid.
            // grid.store.remove(task);

            task.setStartDate(date, true);
            // task.startDate = date;
            task.resource = context.resource;
            scheduler.eventStore.add(task);
          }

          // Dropped on a scheduled event, display toast
          if (targetEventRecord) {
            WidgetHelper.toast(`Dropped on ${targetEventRecord.name}`);
          }

          context.finalize();
        }
        else {
          me.abort();
        }

        scheduler.element.classList.remove('b-dragging-event');

      } // eo drop listener
    })
  } // eo function initDrag

  rescheduleOverlappingTasks(eventRecord, eventStore) {
    if (eventRecord.resource) {
      const
        futureEvents = [],
        earlierEvents = [];

      // Split tasks into future and earlier tasks, ignoring tasks in the past
      eventRecord.resource.events.forEach(event => {
        // Don't modify events in the past
        if (event.endDate > Date.now() && event !== eventRecord) {
          if (event.startDate >= eventRecord.startDate) {
            futureEvents.push(event);
          }
          else {
            earlierEvents.push(event);
          }
        }
      });

      if (futureEvents.length || earlierEvents.length) {
        futureEvents.sort((a, b) => a.startDate > b.startDate ? 1 : -1);
        earlierEvents.sort((a, b) => a.startDate > b.startDate ? -1 : 1);

        futureEvents.forEach((ev, i) => {
          const prev = futureEvents[i - 1] || eventRecord;

          ev.startDate = DateHelper.max(prev.endDate, ev.startDate);
        });

        // Walk backwards and remove any overlap
        [eventRecord, ...earlierEvents].forEach((ev, i, all) => {
          const prev = all[i - 1];

          if (ev.endDate > Date.now() && ev !== eventRecord && prev) {
            ev.setEndDate(DateHelper.min(prev.startDate, ev.endDate), true);
          }
        });

        eventStore.endBatch();
      }
    }
  }

  // Uncomment the code in this method to start "logging" events
  onSchedulerEvents(event: any) {
    //   // catch scheduler events here, for example when dropping an event:
    if (event.type === 'eventdrop') {
      // console.log('Drop: ', event); 
      // Toast.show('event has been dropped');

    }
    if (event.type === 'eventschange') {
      console.log('eventchanged');
    }
    //
    //   // or when editing one:
    //   if (event.type === 'eventschange') {
    //     console.log('EventStore: ', event);
    //   }
    //
    //   // or when editing a resource:
    //   if (event.type === 'resourceschange') {
    //     console.log('ResourceStore: ', event);
    //   }
  }


  onZoomIn() {
    this.scheduler.schedulerEngine.zoomIn();
  } // eo function onZoomIn

  /**
   * Handles zoom-out click event
   * @param event
   */
  onZoomOut() {
    this.scheduler.schedulerEngine.zoomOut();
  } // eo function onZoomOut



  // add event button click handled here
  onAddEventClick() {
    this.scheduler.addEvent();
  }

  // remove event button click handled here
  onRemoveEventClick() {
    this.scheduler.removeEvent();
  }

  // change scheduler start/end dates
  onDatePickerChange({ value }) {
    this.scheduler.schedulerEngine.setTimeSpan(DateHelper.add(value, 8, 'hour'), DateHelper.add(value, 18, 'hour'));
  }

  filterEvents(value) {
    value = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

    this.scheduler.schedulerEngine.eventStore.filter({
      filters: event => event.name.match(new RegExp(value, 'i')),
      replace: true
    });
  }
}


Post by mats »

This is a lot of unrelated irrelevant code, please refrain from pasting such code here. Please create a small clear test case and upload it here and describe your issue clearly ("but does not seem to work" is a bad way of communicating your expectations)

Post by Dev@2609 »

Mats, I want to use the autoreschedule tasks feature so that events get autoshifted when 1 event is dropped on other. I reffered to the drag from grid demo in angular
eventStore.on({
      update: ({ record, changes }) => {
        if ('resourceId' in changes && !record.resourceId) {
          eventStore.remove(record);
          //  grid.store.add(record);
        }
        if (this.autoRescheduleTasks) {
          this.rescheduleOverlappingTasks(record, eventStore);
          // eventStore.beginBatch();
        }
      },
      add: ({ records }) => {

        if (this.autoRescheduleTasks) {
          eventStore.beginBatch();
        }

        if (this.autoRescheduleTasks) {
          records.forEach((eventRecord) => this.rescheduleOverlappingTasks(eventRecord, eventStore));
          eventStore.endBatch();
        }
      }
    });
    
    rescheduleOverlappingTasks(eventRecord, eventStore) {
    if (eventRecord.resource) {
      const
        futureEvents = [],
        earlierEvents = [];

      // Split tasks into future and earlier tasks, ignoring tasks in the past
      eventRecord.resource.events.forEach(event => {
        // Don't modify events in the past
        if (event.endDate > Date.now() && event !== eventRecord) {
          if (event.startDate >= eventRecord.startDate) {
            futureEvents.push(event);
          }
          else {
            earlierEvents.push(event);
          }
        }
      });

      if (futureEvents.length || earlierEvents.length) {
        futureEvents.sort((a, b) => a.startDate > b.startDate ? 1 : -1);
        earlierEvents.sort((a, b) => a.startDate > b.startDate ? -1 : 1);

        futureEvents.forEach((ev, i) => {
          const prev = futureEvents[i - 1] || eventRecord;

          ev.startDate = DateHelper.max(prev.endDate, ev.startDate);
        });

        // Walk backwards and remove any overlap
        [eventRecord, ...earlierEvents].forEach((ev, i, all) => {
          const prev = all[i - 1];

          if (ev.endDate > Date.now() && ev !== eventRecord && prev) {
            ev.setEndDate(DateHelper.min(prev.startDate, ev.endDate), true);
          }
        });

        eventStore.endBatch();
      }
    }
    
    
    

Post by mats »

Ok, it works fine in the demo - maybe you made some change that caused it to not work? Try to debug it?

Post Reply