Our pure JavaScript Scheduler component


Post by jqueija »

Hi Sergey thanks for replying. I tried to implement what was suggested but without any success... I'm copying the code of my component, I've added some comments for you to check where I implemented it to see if you can help me detect where is where I went wrong. My config is already inside my component:

import React, { useCallback, useRef, useEffect, useState, lazy } from 'react';
import { useDispatch, useSelector } from 'react-redux'
import {
    BryntumScheduler,
} from '@bryntum/schedulerpro-react';

import { DateHelper, Menu, ResourceModel } from '@bryntum/schedulerpro'
// import ResourceModel from '@bryntum/schedulerpro/source/lib/SchedulerPro/model'
import '../../styles/styles.scss'

import {
    isLoadingResourcesTypes,
    isLoadingSchedulerResources,
    loadResourcesTypes,
    loadSchedulerResources
} from '../../redux/selectors';

import {
    getResourcesSchedulerSearch,
    getResourcesTypes,
    setEmptyGetResourcesSchedulerSearch,
    setEmptyResourcesTypes,
    setFormFilters,
    setLoadSpinner,
    setUnLoadSpinner
} from '../../redux/actions';

import {
    convertToTranslatedResourceType,
    errorAlert,
    getMessageTranslation
} from '../../utils';

import {
    buildFiltersResourcesSchedulerSearchBusiness,
    initModelResourcesSchedulerSearchBusiness
} from '../../business';

const TaskSchedulerSearchResourcesPopup = lazy(() => import('../TaskSchedulerSearchResourcesPopup'))
const Popup = lazy(() => import('../../components/Popup'))

const SchedulerPro = ({
    user,
    fieldsPage,
    translation,
    language,
    scenario,
    localization,
    userConfiguration,
    companies,
    clientsClassesScenaries,
    onTranslation,
    messages,
    usersCostCenters
}) => {
    const dispatch = useDispatch()
    const schedulerResources = useSelector(state => loadSchedulerResources(state))
    const schedulerResourcesIsLoading = useSelector(state => isLoadingSchedulerResources(state))
    const resourcesTypes = useSelector(state => loadResourcesTypes(state))
    const resourcesTypesIsLoading = useSelector(state => isLoadingResourcesTypes(state))

const [schedulerResourcesResults, setSchedulerResourcesResults] = useState([])
const [openResourcesSchedulerSearchPopup, setOpenResourcesSchedulerSearchPopup] = useState(false)
const [cecos, setCecos] = useState([])
const [searchModel, setSearchModel] = useState(initModelResourcesSchedulerSearchBusiness())

useEffect(() => {
    initializeSelectors()
    handleSearch({ ...searchModel, scenaryId: scenario.id, userId: user.id })
}, [])

useEffect(() => {
    if (resourcesTypes.length === 0 && !resourcesTypesIsLoading) {
        dispatch(getResourcesTypes({ isActive: true }))
    }
    if (schedulerResourcesIsLoading) {
        dispatch(setLoadSpinner())
    } else {
        setSchedulerResourcesResults(schedulerResources.map(sr => {
            return {
                id: sr.id,
                name: sr.name,
                resourceTypeId: convertToTranslatedResourceType(resourcesTypes, sr.resourceTypeId, language.id)
                // initials: sr.name.getInitials().toUpperCase() // ver esto si queremos reducir a x digitos segun nombres y apellidos
            }
        }))
        dispatch(setUnLoadSpinner())
    }
}, [schedulerResources, schedulerResourcesIsLoading, resourcesTypes])

const initializeSelectors = useCallback(() => {
    dispatch(setEmptyGetResourcesSchedulerSearch())
    dispatch(setEmptyResourcesTypes())
}, [fieldsPage])

const handleSearch = (filters) => {
    setSearchModel(filters)
    dispatch(setEmptyGetResourcesSchedulerSearch())
    const businessResult = buildFiltersResourcesSchedulerSearchBusiness(filters)
    if (!businessResult.hasErrors) {
        dispatch(setFormFilters(businessResult.content))
        dispatch(getResourcesSchedulerSearch(businessResult.content))
    } else {
        errorAlert(getMessageTranslation(language.id, messages, 'code', businessResult.resultOperations[0].message))
    }
}

const handleOnPopupSearch = (qualificationName, costCenterIds) => {
    setOpenResourcesSchedulerSearchPopup(false)
    handleSearch({ ...searchModel, qualificationName: qualificationName, costCenterIds: costCenterIds })
}

const handleOnOpenResourcesSchedulerSearchPopup = () => {
    setOpenResourcesSchedulerSearchPopup(true)
}

const handleClearClick = () => {
    setCecos([])
    handleSearch({ ...initModelResourcesSchedulerSearchBusiness(), scenaryId: scenario.id, userId: user.id })
}

   class MyResource extends ResourceModel { // implementation of what was suggested
        get initials() {
            return this.name.substring(0, 2);
        }
    }

const schedulerConfig = !!schedulerResources && {
    eventStyle: 'colored',
    eventColor: null,
    resourceImagePath: null,
    columns: [
        {
            type: 'resourceInfo',
            text: onTranslation(language.id, fieldsPage, "resourceList_filter_orderBy_name", scenario.id),
            width: '11.5rem',
            editor: false,
            filterable: {
                filterField: {
                    placeholder: onTranslation(language.id, fieldsPage, "resourceList_filter_tooltip_orderBy", scenario.id)
                }
            }
        },
        {
            text: onTranslation(language.id, fieldsPage, "resourceList_filter_orderBy_resourceType", scenario.id),
            field: 'resourceTypeId',
            width: '8rem',
            editor: false,
            filterable: {
                filterField: {
                    placeholder: onTranslation(language.id, fieldsPage, "resourceList_filter_tooltip_resourceType", scenario.id)
                }
            }
        },
        {
            width: '1rem',
            type: 'widget',
            widgets: [{
                cls: 'b-transparent',
                type: 'button',
                tooltip: onTranslation(language.id, fieldsPage, "resourceList_resource_btn_members", scenario.id),
                icon: 'b-fa b-fa-fw b-fa-ellipsis-v',
                onAction: ({ source: btn }) => {
                    const menu = btn.menu || (btn.menu = new Menu({
                        cls: 'b-transparent',
                        forElement: btn.element,
                        items: [
                            {
                                cls: 'b-transparent',
                                text: onTranslation(language.id, fieldsPage, "resourceList_resource_btn_members", scenario.id),
                                onItem({ item }) {
                                    console.log('showing team members', item);
                                }
                            }
                        ],
                        focusOnHover: false,
                    }));
                    menu.show();
                }
            }]
        },
    ],
    resourceStore: {
        modelClass: MyResource,   // <--------- class override done see ln 121
        // data: schedulerResourcesResults.map(sr => (sr))   // <--------- I try to implement this which is the same method y use in Ln 206 to load data from a local state
    },
    filterBarFeature: true,
    stripeFeature: true,
    timeRangesFeature: true,
    eventEditFeature: {
        items: {
            locationField: {
                type: 'text',
                name: 'location',
                label: 'Location',
                dataset: { eventType: 'Meeting' },
                weight: 200
            }
        }
    },

    barMargin: 5,
    rowHeight: 40,
    startDate: new Date(2017, 1, 7, 8), // year, monthIndex (starts with 0), day, hours
    endDate: new Date(2017, 1, 7, 19),
    viewPreset: 'hourAndDay',
    resources: schedulerResourcesResults.map(sr => (sr)), //Here is where I insert my data
    events: [ // events are still just a mockup
        {
            resourceId: 3132,
            name: "Make marketing plan",
            startDate: "2017-02-07 11:00",
            endDate: "2017-02-07 14:00"
        },
        {
            resourceId: 3131,
            name: "Read spec.",
            startDate: "2017-02-07 12:00",
            endDate: "2017-02-07 15:00"
        },
        {
            resourceId: 3126,
            name: "Sign documents",
            startDate: "2017-02-07 13:00",
            endDate: "2017-02-07 16:00"
        },
        {
            resourceId: 2705,
            name: "Board meeting",
            startDate: "2017-02-07 09:00",
            endDate: "2017-02-07 11:00"
        },
        {
            resourceId: 3147,
            name: "Sales call",
            startDate: "2017-02-07 10:00",
            endDate: "2017-02-07 12:00"
        },
        {
            resourceId: 3155,
            name: "Customer visit",
            startDate: "2017-02-07 11:00",
            endDate: "2017-02-07 13:00",
            location: "Customer office"
        },
        {
            resourceId: 3118,
            name: "Prepare happy hour",
            startDate: "2017-02-07 14:00",
            endDate: "2017-02-07 17:00"
        },
        {
            resourceId: 3116,
            name: "Sales call",
            startDate: "2017-02-07 14:00",
            endDate: "2017-02-07 17:00"
        }
    ],
    tbar: [
        {
            type: 'button',
            tooltip: onTranslation(language.id, fieldsPage, "resourceList_filter_tooltip_popup", scenario.id),
            cls: 'b-transparent',
            icon: 'b-fa-search',
            align: 'left',
            onClick: (e) => handleOnOpenResourcesSchedulerSearchPopup()
        },
        {
            type: 'button',
            tooltip: onTranslation(language.id, fieldsPage, "resourceList_filter_tooltip_deleteFilters", scenario.id),
            cls: 'b-transparent',
            icon: 'b-fa-trash',
            align: 'left',
            onClick: (e) => handleClearClick()
        },
    ],

    // Specialized body template with header and footer
    eventBodyTemplate: data => `
    <div class="b-sch-event-header">${data.headerText}</div>
    <div class="b-sch-event-footer">${data.footerText}</div>
    `,

    eventRenderer({ eventRecord, resourceRecord, renderData }) {
        renderData.style = 'background-color:' + resourceRecord.color;
        return {
            headerText: DateHelper.format(eventRecord.startDate, this.displayDateFormat),
            footerText: eventRecord.name || ''
        };
    },
};

const scheduler = useRef(null);

return (
    <>
        <BryntumScheduler
            ref={scheduler} {...schedulerConfig}
            style={{ headerHeight: '1rem' }}
        ></BryntumScheduler>
        <Popup
            openPopup={openResourcesSchedulerSearchPopup}
            setOpenPopup={setOpenResourcesSchedulerSearchPopup}
            title='Buscador de Recursos' // falta traducción del título
            maxWidth={"sm"}
        >
            <TaskSchedulerSearchResourcesPopup
                fieldsPage={fieldsPage}
                translation={translation}
                language={language}
                scenario={scenario}
                localization={localization}
                userConfiguration={userConfiguration}
                companies={companies}
                clientsClassesScenaries={clientsClassesScenaries}
                onTranslation={onTranslation}
                messages={messages}
                usersCostCenters={usersCostCenters}
                handleOnPopupSearch={handleOnPopupSearch}
                searchModel={searchModel}
                cecos={cecos}
                setCecos={setCecos}
            />
        </Popup>
    </>
)
}

export default React.memo(SchedulerPro)

Also I'm attaching a capture of the error is causing on my saga. If I remove the code you suggested and just work loading my data inside the prop 'resources' it works but of course without being able to modify or alter the initials.

Thanks and I look forward to hearing from you,

Attachments
Capture02.PNG
Capture02.PNG (82.62 KiB) Viewed 1275 times

Post by sergey.maltsev »

Hi!

You can't use

resources: schedulerResourcesResults.map(sr => (sr)), //Here is where I insert my data

alongside with

    resourceStore: {
        modelClass: MyResource,   // <--------- class override done see ln 121
        // data: schedulerResourcesResults.map(sr => (sr))   // <--------- I try to implement this which is the same method y use in Ln 206 to load data from a local state
    },

resources is a shorthand config for resourceStore.data

Please remove resources from config and use data under resourceStore.

If you still have any problems please attach full app code we can check and help you.
If you use React please attach full project to install and build


Post by jqueija »

Hi Sergey, actually I left it uncommented but I wasn't using it while using the prop 'data', which shows commented on the piece of code that I sent but just because it was like that at the time I took the capture. Sadly for as much as I'd love to send the full app, due to copyrights of my company I am unable to do so. The important thing inside the component code I sent was to look if the implementation was correct. I'll rewrite the last post showing what you need to see and commenting the resource prop, the result I am getting is exactly the same ( I get a white screen and a bryntum related type of error which I'll attach to this reply).

import React, { useCallback, useRef, useEffect, useState, lazy } from 'react';
import { useDispatch, useSelector } from 'react-redux'
import {
    BryntumScheduler,
} from '@bryntum/schedulerpro-react';

import { DateHelper, Menu, ResourceModel } from '@bryntum/schedulerpro'
import '../../styles/styles.scss'

import {
    isLoadingQualifications,
    isLoadingResourcesTypes,
    isLoadingSchedulerResources,
    loadQualifications,
    loadResourcesTypes,
    loadSchedulerResources
} from '../../redux/selectors';

import {
    getQualifications,
    getResourcesSchedulerSearch,
    getResourcesTypes,
    setEmptyGetResourcesSchedulerSearch,
    setEmptyResourcesTypes,
    setFormFilters,
    setLoadSpinner,
    setUnLoadSpinner
} from '../../redux/actions';

import {
    convertToTranslatedResourceType,
    errorAlert,
    getMessageTranslation
} from '../../utils';

import {
    buildFiltersResourcesSchedulerSearchBusiness,
    initModelResourcesSchedulerSearchBusiness
} from '../../business';

const TaskSchedulerSearchResourcesPopup = lazy(() => import('../TaskSchedulerSearchResourcesPopup'))
const Popup = lazy(() => import('../../components/Popup'))

const SchedulerPro = ({
    user,
    fieldsPage,
    translation,
    language,
    scenario,
    localization,
    userConfiguration,
    companies,
    clientsClassesScenaries,
    onTranslation,
    messages,
    usersCostCenters
}) => {
    const dispatch = useDispatch()
    const schedulerResources = useSelector(state => loadSchedulerResources(state))
    const schedulerResourcesIsLoading = useSelector(state => isLoadingSchedulerResources(state))
    const resourcesTypes = useSelector(state => loadResourcesTypes(state))
    const resourcesTypesIsLoading = useSelector(state => isLoadingResourcesTypes(state))
    const qualifications = useSelector(state => loadQualifications(state))
    const qualificationsIsLoading = useSelector(state => isLoadingQualifications(state))

const [schedulerResourcesResults, setSchedulerResourcesResults] = useState([])
const [openResourcesSchedulerSearchPopup, setOpenResourcesSchedulerSearchPopup] = useState(false)
const [cecos, setCecos] = useState([])
const [searchModel, setSearchModel] = useState(initModelResourcesSchedulerSearchBusiness())

useEffect(() => {
    initializeSelectors()
    handleSearch({ ...searchModel, scenaryId: scenario.id, userId: user.id })
}, [])

useEffect(() => {
    if (resourcesTypes.length === 0 && !resourcesTypesIsLoading) {
        dispatch(getResourcesTypes({ isActive: true }))
    }
    if (qualifications.length === 0 && !qualificationsIsLoading) {
        dispatch(getQualifications({scenaryId: scenario.id}))
    }
    if (schedulerResourcesIsLoading) {
        dispatch(setLoadSpinner())
    } else {
        setSchedulerResourcesResults(schedulerResources.map(sr => {
            return {
                id: sr.id,
                name: sr.name,
                resourceTypeId: convertToTranslatedResourceType(resourcesTypes, sr.resourceTypeId, language.id)
                // initials: sr.name.getInitials().toUpperCase() // ver esto si queremos reducir a x digitos segun nombres y apellidos
            }
        }))
        dispatch(setUnLoadSpinner())
    }
}, [schedulerResources, schedulerResourcesIsLoading, resourcesTypes, qualifications])

const initializeSelectors = useCallback(() => {
    dispatch(setEmptyGetResourcesSchedulerSearch())
    dispatch(setEmptyResourcesTypes())
}, [fieldsPage])

const handleSearch = (filters) => {
    setSearchModel(filters)
    dispatch(setEmptyGetResourcesSchedulerSearch())
    const businessResult = buildFiltersResourcesSchedulerSearchBusiness(filters)
    if (!businessResult.hasErrors) {
        dispatch(setFormFilters(businessResult.content))
        dispatch(getResourcesSchedulerSearch(businessResult.content))
    } else {
        errorAlert(getMessageTranslation(language.id, messages, 'code', businessResult.resultOperations[0].message))
    }
}

const handleOnPopupSearch = (qualificationName, costCenterIds) => {
    setOpenResourcesSchedulerSearchPopup(false)
    handleSearch({ ...searchModel, qualificationName: qualificationName, costCenterIds: costCenterIds })
}

const handleOnOpenResourcesSchedulerSearchPopup = () => {
    setOpenResourcesSchedulerSearchPopup(true)
}

const handleClearClick = () => {
    setCecos([])
    handleSearch({ ...initModelResourcesSchedulerSearchBusiness(), scenaryId: scenario.id, userId: user.id })
}
class MyResource extends ResourceModel { // implementation of what was suggested
    get initials() {
        return this.name.substring(0, 2);
    }
}

const schedulerConfig = !!schedulerResources && {
    eventStyle: 'colored',
    eventColor: null,
    resourceImagePath: null,
    columns: [
        {
            type: 'resourceInfo',
            text: onTranslation(language.id, fieldsPage, "resourceList_filter_orderBy_name", scenario.id),
            width: '11.5rem',
            editor: false,
            filterable: {
                filterField: {
                    placeholder: onTranslation(language.id, fieldsPage, "resourceList_filter_tooltip_orderBy", scenario.id)
                }
            }
        },
        {
            text: onTranslation(language.id, fieldsPage, "resourceList_filter_orderBy_resourceType", scenario.id),
            field: 'resourceTypeId',
            width: '9rem',
            editor: false,
            filterable: {
                filterField: {
                    placeholder: onTranslation(language.id, fieldsPage, "resourceList_filter_tooltip_resourceType", scenario.id)
                }
            }
        },
        {
            width: '1rem',
            type: 'widget',
            widgets: [{
                cls: 'b-transparent',
                type: 'button',
                tooltip: onTranslation(language.id, fieldsPage, "resourceList_resource_btn_members", scenario.id),
                icon: 'b-fa b-fa-fw b-fa-ellipsis-v',
                onAction: ({ source: btn }) => {
                    const menu = btn.menu || (btn.menu = new Menu({
                        cls: 'b-transparent',
                        forElement: btn.element,
                        items: [
                            {
                                cls: 'b-transparent',
                                text: onTranslation(language.id, fieldsPage, "resourceList_resource_btn_members", scenario.id),
                                onItem({ item }) {
                                    console.log('showing team members', item);
                                }
                            }
                        ],
                        focusOnHover: false,
                    }));
                    menu.show();
                }
            }]
        },
    ],
    resourceStore: {
        modelClass: MyResource,   // <--------- class override done see ln 121
        data: schedulerResourcesResults.map(sr => (sr))   // <--------- I try to implement this which is the same method y use in Ln 206 to load data from a local state
    },
    filterBarFeature: true,
    stripeFeature: true,
    timeRangesFeature: true,
    eventEditFeature: {
        items: {
            locationField: {
                type: 'text',
                name: 'location',
                label: 'Location',
                dataset: { eventType: 'Meeting' },
                weight: 200
            }
        }
    },
    barMargin: 5,
    rowHeight: 40,
    startDate: new Date(2017, 1, 7, 8), // year, monthIndex (starts with 0), day, hours
    endDate: new Date(2017, 1, 7, 19),
    viewPreset: 'hourAndDay',
    events: [ // events are still just a mockup
        {
            resourceId: 3132,
            name: "Make marketing plan",
            startDate: "2017-02-07 11:00",
            endDate: "2017-02-07 14:00"
        },
        {
            resourceId: 3131,
            name: "Read spec.",
            startDate: "2017-02-07 12:00",
            endDate: "2017-02-07 15:00"
        },
        {
            resourceId: 3126,
            name: "Sign documents",
            startDate: "2017-02-07 13:00",
            endDate: "2017-02-07 16:00"
        },
        {
            resourceId: 2705,
            name: "Board meeting",
            startDate: "2017-02-07 09:00",
            endDate: "2017-02-07 11:00"
        },
        {
            resourceId: 3147,
            name: "Sales call",
            startDate: "2017-02-07 10:00",
            endDate: "2017-02-07 12:00"
        },
        {
            resourceId: 3155,
            name: "Customer visit",
            startDate: "2017-02-07 11:00",
            endDate: "2017-02-07 13:00",
            location: "Customer office"
        },
        {
            resourceId: 3118,
            name: "Prepare happy hour",
            startDate: "2017-02-07 14:00",
            endDate: "2017-02-07 17:00"
        },
        {
            resourceId: 3116,
            name: "Sales call",
            startDate: "2017-02-07 14:00",
            endDate: "2017-02-07 17:00"
        }
    ],
    tbar: [
        {
            type: 'button',
            tooltip: onTranslation(language.id, fieldsPage, "resourceList_filter_tooltip_popup", scenario.id),
            cls: 'b-transparent',
            icon: 'b-fa-search',
            align: 'left',
            onClick: (e) => handleOnOpenResourcesSchedulerSearchPopup()
        },
        {
            type: 'button',
            tooltip: onTranslation(language.id, fieldsPage, "resourceList_filter_tooltip_deleteFilters", scenario.id),
            cls: 'b-transparent',
            icon: 'b-fa-trash',
            align: 'left',
            onClick: (e) => handleClearClick()
        },
    ],

    // Specialized body template with header and footer
    eventBodyTemplate: data => `
    <div class="b-sch-event-header">${data.headerText}</div>
    <div class="b-sch-event-footer">${data.footerText}</div>
    `,

    eventRenderer({ eventRecord, resourceRecord, renderData }) {
        renderData.style = 'background-color:' + resourceRecord.color;
        return {
            headerText: DateHelper.format(eventRecord.startDate, this.displayDateFormat),
            footerText: eventRecord.name || ''
        };
    },
};

const scheduler = useRef(null);

return (
    <>
        <BryntumScheduler
            ref={scheduler} {...schedulerConfig}
            style={{ headerHeight: '1rem' }}
        ></BryntumScheduler>
        <Popup
            openPopup={openResourcesSchedulerSearchPopup}
            setOpenPopup={setOpenResourcesSchedulerSearchPopup}
            title='Buscador de Recursos' // falta traducción del título
            maxWidth={"sm"}
        >
            <TaskSchedulerSearchResourcesPopup
                fieldsPage={fieldsPage}
                translation={translation}
                language={language}
                scenario={scenario}
                localization={localization}
                userConfiguration={userConfiguration}
                companies={companies}
                clientsClassesScenaries={clientsClassesScenaries}
                onTranslation={onTranslation}
                messages={messages}
                usersCostCenters={usersCostCenters}
                handleOnPopupSearch={handleOnPopupSearch}
                searchModel={searchModel}
                cecos={cecos}
                setCecos={setCecos}
            />
        </Popup>
    </>
)
}

export default React.memo(SchedulerPro)

Is there any other way you can help me without having me to send the build version of the app?. The app is quite basic yet as I am just getting started implementing bryntum scheduler pro to it so there isn't much to show actually. I have the component and the configuration in one single piece of code which is what I am sharing with you.

Thanks once again and I hope you can help me get through this,

Attachments
Capture03expanding error.PNG
Capture03expanding error.PNG (81.62 KiB) Viewed 1271 times
Capture03.PNG
Capture03.PNG (69.31 KiB) Viewed 1271 times

Post by sergey.maltsev »

Hi!

It is hard to comment on what's could be wrong there without app code.
You may use forums's PM to give me some copyright sensitive data.

My code works fine in pure JS and I've checked it before advising you.

Would be enough if you just try using one of our bundled React examples with the changes I gave you above to check how it works. Add only changes for initials and resource image support which is in this port advised by Alex and me.

Or just wrap the code you've attached above into some very basic React App where you could reproduce the error and give it to us.


Post by jqueija »

Hi Sergey,

I don't understand what you said about reproducing the error on your last line. I've sent a capture photo, the component and a few messages back, I've sent Mats how data structure is arriving to the scheduler. I started my Scheduler from one of your examples so I haven't modified anything except what you see here. I included the config file inside the component so I had everything in one place. Other than that I don't know what else to send you. The error is as shown on my previous message.

Thanks once again,


Post by sergey.maltsev »

Hi!

I've just meant that we need some simple app to see the error.
The error might come from resources data itself, so please check if you provide the correct one.

Please check the attached zip which includes the most essential React app for SchedulerPro to show avatar's initials() handling mentioned above in this post.

avatars.zip
(5.36 KiB) Downloaded 78 times

Just

npm i
npm run start

Should look like this

Avatars.png
Avatars.png (38.01 KiB) Viewed 1265 times

Post by peaguilar »

Is there a known way to override this getInitials function while using typescript? I get this error below when I try to override the getInitials method

Screen Shot 2022-09-19 at 9.04.45 AM.png
Screen Shot 2022-09-19 at 9.04.45 AM.png (56.34 KiB) Viewed 959 times

Post by marcio »

Hey peaguilar,

Thanks for the report, I wrote a ticket for it https://github.com/bryntum/support/issues/5284

Best regards,
Márcio


Post by sergey.maltsev »

Hello, peaguilar!

While initials is declared as property you may use this

import { ResourceModel, ResourceModelConfig } from '@bryntum/scheduler';

class MyResource extends ResourceModel {
    constructor(config?: Partial<ResourceModelConfig>) {
        super(config);
        Object.defineProperty(this, 'initials', {
            get() {
                return this.name.substring(0, 2);
            }
        });
    }
}

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty


Post by peaguilar »

Thank you


Post Reply