Our pure JavaScript Scheduler component


Post by jnarowski »

First of all, I wanted to thank you all for being so helpful with my many questions. It's super helpful for this integration. I'm doing a rapid proof-of-concept to hand over to my developer, so really trying to work fast and get this out to him (and confirm we want to buy this!)

Right now, I'm getting a bunch of errors upon dragging event in the calendar scheduler view:
calendar.module.js:53 Uncaught TypeError: Cannot read properties of undefined (reading 'isAssignment')

See here:
https://jmp.sh/w72eIDpR

Any suggestions as to where this might be originating from in the config? The source code being minified makes it a bit tough to troubleshoot.

I am trying to hook into event drag events to trigger the same experience that I am building on the calendar side.


Post by Animal »

What is going on during that drag?

I can drag events around in the Scheduler view here: https://bryntum.com/products/calendar/examples/calendar-scheduler/

What is different in your app?


Post by jnarowski »

Not sure. Spot anything that might be problematic in this code? I've tried commenting out various config options but it seems to persist.

<script setup lang="ts">
// @ts-nocheck
import '@bryntum/calendar/calendar.stockholm.css'
import { ref, toRefs, computed, onMounted } from 'vue'
import { BryntumCalendar } from '@bryntum/calendar-vue-3'
import { Calendar, DateHelper } from '@bryntum/calendar'

import { PresetManager, StringHelper } from '@bryntum/schedulerpro'

interface iSpectoraCalendarProps {
  config: any
  events: any[]
  resources: any[]
  settings: any
  me: any
}

const convertTime12to24 = (time12h) => {
  const [time, modifier] = time12h.split(' ')

  let [hours, minutes] = time.split(':')

  if (hours === '12') {
    hours = '00'
  }

  if (modifier === 'PM') {
    hours = parseInt(hours, 10) + 12
  }

  return Number(hours)
}

const useCalendarConfig = () => {
  const profileSettings = props.settings.profile.attributes.settings || {}
  const dayStart = convertTime12to24(profileSettings.calendar_start_time)
  const dayEnd = convertTime12to24(profileSettings.calendar_end_time)

  // this.queryParams.type ||
  //       this.profileSettings.calendar_default_view ||
  //       'resourceTimelineDay'
  //     // if (this.isMobile) initialView = 'listWeek'
  //     const slotMinTime = this.profileSettings.calendar_start_time
  //     const slotMaxTime = this.profileSettings.calendar_end_time
  //     const offsetWeeks = this.profileSettings.calendar_offset_weeks
  //     const offsetDays = this.profileSettings.calendar_offset_days

  const baseTimelineConfig = {
    type: 'scheduler',
    displayName: 'Timeline',
    // eventStyle: 'calendar',
    workingTime: {
      fromHour: dayStart,
      toHour: dayEnd,
    },
    features: {
      nonWorkingTime: true,
    },
    columns: [
      {
        type: 'resourceInfo',
        field: 'name',
        text: 'Inspectors',
        width: 175,
        //
        // Custom rendering
        // https://bryntum.com/products/schedulerpro/examples/conflicts/
        //
        // htmlEncode: false,
        // // Renderer that returns a DOM config object, a more performant way than returning a html string, allows
        // // reusing elements as cells are re-rendered
        // renderer: ({ record }) => ({
        //   children: [
        //     // <i> tag with the icon
        //     record.icon
        //       ? {
        //           tag: 'i',
        //           className: `b-fa b-fa-fw ${record.icon}`,
        //           style: 'margin-right: .5em',
        //         }
        //       : null,
        //     // text node with the name
        //     record.name,
        //   ],
        // }),
      },
    ],
  }

  return {
    sidebar: props.config.sidebar,
    mode: 'week',
    features: {
      eventTooltip: {
        // Tooltip configs can be used here
        align: 'l-r', // Align left to right,
        // Custom content
        renderer: (data) => {
          console.log(data, 'abc...')
          return 'I am a tooltip!'
        },
      },
    },
    modes: {
      day: {
        ...baseTimelineConfig,
        stepUnit: 'day',
        displayName: 'Day',
        descriptionRenderer() {
          return DateHelper.format(this.startDate, 'dddd, MMMM Do, YYYY')
        },
        // https://www.bryntum.com/products/scheduler/docs/api/Scheduler/preset/ViewPreset
        viewPreset: {
          base: 'hourAndDay',
          headers: [
            {
              unit: 'hour',
              dateFormat: 'hA',
            },
          ],
        },
      },
      week: {
        ...baseTimelineConfig,
        stepUnit: 'week',
        displayName: 'Week',
        viewPreset: {
          base: 'hourAndDay',
          tickWidth: 45,
          headers: [
            {
              unit: 'day',
              dateFormat: 'ddd MM/DD',
            },
            {
              unit: 'hour',
              dateFormat: 'hA',
            },
          ],
        },
      },
      month: {
        showResourceAvatars: 'last',
        autoRowHeight: true,
      },
      year: false,
    },
  }
}

// const calendar = computed(() => calendarRef.value.instance.value)
const props = withDefaults(defineProps<iSpectoraCalendarProps>(), {
  config: () => ({}),
  settings: () => ({}),
  me: () => ({}),
  resources: () => [],
  events: () => [],
})

const calendarRef = ref(null)
const calendarConfig = ref(useCalendarConfig())

function onAutoRowHeightChanged({ checked }) {
  const calendar = calendarRef.value.instance.value

  calendar.modes.month.autoRowHeight = checked
}

// Called as the resourceFilterFilter's onChange handler
function onResourceFilterFilterChange({ value }) {
  const calendar = calendarRef.value.instance.value
  // A filter with an id replaces any previous filter with that id.
  // Leave any other filters which may be in use in place.
  calendar.widgetMap.resourceFilter.store.filter({
    id: 'resourceFilterFilter',
    filterBy: (r) => r.name.toLowerCase().startsWith(value.toLowerCase()), // a function which returns true to include the record
  })
}

// -------------------------------------------
// Calendar
// -------------------------------------------

const handleEventClick = (options) => {
  console.log('event clicked:', options)
}

const handleBeforeEventEdit = (options) => {
  console.log('handleBeforeEventEdit', options)
  // By returning false from a listener for an event documented as preventable the action that would otherwise be executed after the event is prevented. These events names are usually prefixed with before.
  // return false
}

const handleBeforeDragMoveEnd = (options) => {
  console.log('handleBeforeDragMoveEnd', options)

  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // Trigger reschedule confirmation modal and pass it the promise resolver
      // If confirmed, you can resolve. If rejected you reject
      // https://jmp.sh/15dKSBDi
      if (confirm('Are you sure you want to move this event?')) {
        console.log('confirmed...')
        resolve(true)
      } else {
        console.log('rejected...')
        reject()
      }
    }, 3000)
  })
}

const handleDragMoveEnd = (options) => {
  console.log('handleDragMoveEnd', options)
  return true
}
const handleBeforeDragResizeEnd = (options) => {
  console.log('handleBeforeDragResizeEnd', options)
}
const handleBeforeDragCreateEnd = (options) => {
  console.log('handleBeforeDragCreateEnd', options)
}

// -------------------------------------------
// Scheduler Pro
// -------------------------------------------

const handleBeforeEventDropFinalize = ({ context }) => {
  console.log('handleBeforeEventDropFinalize', options)
  context.valid = true
  return context
}

onMounted(() => {
  const calendar = calendarRef.value.instance.value

  // calendar.widgetMap.autoRowHeight.on({ change: onAutoRowHeightChanged })
  // calendar.widgetMap.resourceFilterFilter.on({
  //   change: onResourceFilterFilterChange,
  // })
})
</script>

<template>
  <bryntum-calendar
    ref="calendarRef"
    :events="events"
    :resources="resources"
    v-bind="calendarConfig"
  />
</template>


Post by jnarowski »

Found the issue. If I comment out viewPreset, it works fine.

Is this below config incorrect?

        viewPreset: {
          base: 'hourAndDay',
          tickWidth: 45,
          headers: [
            {
              unit: 'day',
              dateFormat: 'ddd MM/DD',
            },
            {
              unit: 'hour',
              dateFormat: 'hA',
            },
          ],
        },

This viewPreset config does change the display and seems to have an impact but appears to break event dragging.


Post by jnarowski »

I get the same error if I add a listener for beforeEventResize see https://jmp.sh/lpJ4thCw

Sorry if I am missing something obvious here.

const handleBeforeEventResize = ({ context }) => {
  console.log('beforeEventResize', context)
  context.valid = true
  return context
}

and

  <bryntum-calendar
    ref="calendarRef"
    :events="events"
    :resources="resources"
    v-bind="calendarConfig"
    @before-event-resize="handleBeforeEventResize"
    @before-drag-move-end="handleBeforeDragMoveEnd"
    @drag-move-end="handleDragMoveEnd"
  />

When I remove @before-event-resize="handleBeforeEventResize" the error goes away on drag.

I am having a difficult time with events in general and could really use some clarity. Would it be possible for us to collaborate on a codesandbox or something like that so we can be working from the same place? I would set one up but not sure how we'd do that with your login required private NPM packages.

Would love to jump on a call or a screen share of that's possible to shortcut the back and forth.

I have tried with a variety of naming conventions and datasets and have not successfully emitted an event on drag or resize in the scheduler.

PS: What is the naming convention for listening to events in Vue? Is it different between Scheduler Pro and Calendar? I saw in your docs @eventclick="...." but that was not correct. When I changed it to @event-click="..." it worked.

https://jumpshare.com/v/IdrlaC8KQBAYmYTsVUwg


Post by jnarowski »

Here is a barebones example replicating the viewPreset bug. You shouldn't need anything else to copy this and run it on your side.

https://gist.github.com/jnarowski/988eeb0db7741e86e59fc97402280753

Simply try to drag the event around and you'll see the bug in the console.

This example also shows that none of the events I am listening to are being emitted specifically (@before-event-event-resize and @before-event-drop-finalize).

If you drag or resize an event, neither trigger the handlers (where I have a console.log for each).

Dependencies:

    "@bryntum/calendar": "npm:@bryntum/calendar-trial@5.3.1",
    "@bryntum/calendar-vue-3": "5.3.1",
    "@bryntum/schedulerpro": "npm:@bryntum/schedulerpro-trial@5.3.1",
    "@bryntum/schedulerpro-vue-3": "5.3.1",

Post by marcio »

Hey jnarowski,

As I saw in the gist code, there are some warnings being displayed there regarding our integration with Vue, please check this guide https://bryntum.com/products/calendar/docs/guide/Calendar/integration/vue/guide#features

Also, as you're using Scheduler Pro, you need to configure the Assignment store as well, because Scheduler Pro uses multi-assignment by default, different from normal Scheduler, please check this documentation also https://bryntum.com/products/schedulerpro/docs/api/Scheduler/data/AssignmentStore

Best regards,
Márcio


Post by jnarowski »

Would it be possible to get a full working example of how to fix the warnings and use the assignment store in tandem with the calendar? I'm blocked at the moment as I am not quite following how to fix this issue and use the two together.

This feels like a very important thing to show on your examples page as well .


Post by marcio »

Hey,

Sorry by the confusion, you're using Calendar and not Scheduler Pro in that example, I solved it by changing

{
                    id: 2,
                    name: '3535 Quebec St, Denver 80207',
                    resourceId: ['25'],
                    startDate: '2023-03-23T08:00:00.000-06:00',
                    endDate: '2023-03-23T10:30:00.000-06:00'
                }

to

// Change the id to a number
{
                    id: 2,
                    name: '3535 Quebec St, Denver 80207',
                    resourceId: 25,
                    startDate: '2023-03-23T08:00:00.000-06:00',
                    endDate: '2023-03-23T10:30:00.000-06:00'
                }

And regarding the warning, instead of having

features: {
      eventTooltip: {
        // Tooltip configs can be used here
        align: 'l-r', // Align left to right,
        // Custom content
        renderer: (data) => {
          return 'I am a tooltip!'
        },
      },
    },

We have the features directly configured into the component, as you'll see in the Vue Guide documentation that I shared earlier.


<bryntum-calendar
        :eventTooltipFeature="eventFeatureConfig"
    />
    
// other configuration const eventFeatureConfig = { // Tooltip configs can be used here align: 'l-r', // Align left to right, // Custom content renderer: () => { return 'I am a tooltip!'; } };

Best regards,
Márcio


Post by jnarowski »

This is really great. Thank you!

I made the changes you suggested, which appeared to fix the errors.

However, I am still experiencing these issues:

  • I am still not seeing any events emitted from event drag or resize changes in the Scheduler view of the Calendar. Would you be able to provide an example of how to do this correctly?
  • Multiple inspectors can be assigned to a given event, which is why I used the resourceId as an array. In our current calendar, they appear in both resource rows. How would I accomplish this same behavior here?

BTW, a small suggestion: This was not clear to me that this section translated to this on the wrapper:
https://jmp.sh/EDBjeJoD

    :event-tooltip-feature="{
      align: 'l-r',
      renderer: (data) => {
        console.log(data, 'abc...')
        return 'I am a tooltip!'
      },
    }"

A code example would really help with clarity!


Post Reply