Premium support for our pure JavaScript UI components


Post by rocketreading »

I used the code below in CodePen and it didn't work for me.

As per my code, it should have been marked as blocked for the march date which is specified in the calendar that I used in Resource.

Do let me know, if I am doing something incorrect here.

document.body.classList.add("b-theme-stockholm"); // for styling purposes
import * as Module from 'https://bryntum.com/products/gantt/build/gantt.module.js?485314';
Object.assign(window, Module);
// The code above imports module bundle and places all Bryntum classes on window to simplify coding at CodePen

//region "lib/Task.js"

// Subclass standard TaskModel to add an extra field
// indicating special tasks representing projects
class Task extends TaskModel {

static $name = 'Task';

static fields = [
    { name : 'isProjectTask', type : 'boolean' }
];

// Returns the project the task belongs to
get projectTask() {
    let result = null;

    // proceed the task hierarchy to the root
    this.bubbleWhile(task => {
        // if current task is a project
        if (task.isProjectTask) {
            result = task;
        }

        // stop when project is found or we got to the root
        return !result && task.parent && !task.parent.isRoot;
    });

    return result;
}
}

//endregion

const project = new ProjectModel({
    taskModelClass : Task,

loadUrl      : 'data/load.json',
startDate    : '2024-01-16',
calendar     : 'general',
daysPerWeek  : 7,
daysPerMonth : 30,
hoursPerDay  : 24,
calendars    : [
    {
        id        : 'general',
        name      : 'General',
        intervals : [
            {
                recurrentStartDate : 'on Sat',
                recurrentEndDate   : 'on Mon',
                isWorking          : false
            }
        ]
    },
    {
        id                       : 'late',
        name                     : 'Day shift',
        intervals                : [
            {
                startDate : '2025-03-05',
                    endDate   : '2025-03-06',
                    isWorking : false
            }
        ]
    }
],
assignments : [
    {
        id       : 1,
        event    : 11,
        resource : 1,
        units    : 100
    }
],
resources : [
    { id : 1, name : "Celia's team", city : 'Barcelona', calendar : 'late', image : 'celia.jpg', maxUnits : 300 }
],
tasks : [
    {
        id             : 1000,
        startDate      : '2024-01-16',
        name           : 'Project A',
        isProjectTask  : true,
        note           : 'Project A description',
        percentDone    : 48.627450980392155,
        duration       : 20,
        iconCls        : 'b-icon projectIcon',
        expanded       : true,
        constraintType : 'startnoearlierthan',
        constraintDate : '2024-01-16',
        children       : [
            {
                id          : 1,
                name        : 'Planning',
                percentDone : 50,
                startDate   : '2024-01-16',
                duration    : 10,
                expanded    : true,
                rollup      : true,
                children    : [
                    {
                        id          : 11,
                        name        : 'Investigate',
                        percentDone : 50,
                        cls         : 'LowPrio',
                        startDate   : '2024-01-16',
                        duration    : 8,
                        segments    : [
                            {
                                id        : 1,
                                startDate : '2024-01-16',
                                duration  : 1
                            },
                            {
                                id        : 2,
                                startDate : '2024-01-18',
                                duration  : 2
                            },
                            {
                                id        : 3,
                                startDate : '2024-01-23',
                                duration  : 5
                            }
                        ]
                    }
                ]
            }
        ]
    }
],
autoSetConstraints : true,

// This config enables response validation and dumping of found errors to the browser console.
// It's meant to be used as a development stage helper only so please set it to false for production systems.
validateResponse : true
});

const gantt = new Gantt({
    project,
    dependencyIdField       : 'sequenceNumber',
    resourceImageFolderPath : '../_shared/images/users/',
    appendTo                : 'container',
    viewPreset              : 'weekAndDayLetter',
    tickSize                : 40,
    columnLines             : true,
    startDate               : '2024-01-16',
    columns                 : [
        { type : 'name', width : 280 },
        { type : 'resourceassignment', showAvatars : true, width : 170 }
    ]
});

new Splitter({
    appendTo : 'container'
});

// prepare array of functions we are going to group the view store by
// (we make a constant since we are going to use it in few places)
const resourceNProjectGroupFns = [
    // group by resource
    ({ origin }) => {
        // If record is a resource means it has no assignments ..since this function is called for leaves only.
        // So further grouping makes no sense for this record - stop grouping.
        if (origin.isResourceModel) {
            return Store.StopBranch;
        }

    return origin.resource;
},
// group by the task project
({ origin }) => {
    // If record is a resource means it has no assignments since this function is called for leaves only.
    // So further grouping makes no sense for this record - stop grouping.
    if (origin.isResourceModel) {
        return Store.StopBranch;
    }

    return origin.event?.projectTask || 'No Project';
}
];

const resourceUtilization = new ResourceUtilization({
    appendTo                : 'container',
    project,
    partner                 : gantt,
    rowHeight               : 40,
    showBarTip              : true,
    resourceImageFolderPath : '../_shared/images/users/',
    features                : {
        treeGroup : {
            levels : resourceNProjectGroupFns
        }
    },

// Display either allocated time (default) or time left in bars
getBarText({ effort, maxEffort }, index, series, renderData) {
    const view = this.owner;

    // default text
    let result = view.getBarTextDefault(...arguments);

    // If we have "Show time left" checked
    // and both spent and max time are provided
    if (view.widgetMap.showTimeLeft.checked && maxEffort && effort) {

        const unit = view.getBarTextEffortUnit();

        // display available time left
        result = view.getEffortText(Math.max(maxEffort - effort, 0), unit);
    }

    return result;
},

listeners : {
    // Let's scroll Gantt to the corresponding task
    // when clicking a row representing an assignment or a project
    cellClick({ grid, record }) {
        record = grid.resolveRecordToOrigin(record);

        const task = record.isAssignmentModel ? record.event : record.key?.isProjectTask ? record.key : null;

        if (task) {
            gantt.scrollTaskIntoView(task, {
                highlight : true,
                animate   : {
                    easing   : 'easeFromTo',
                    duration : 2000
                }
            });
        }
    }
},

columns : [
    {
        type  : 'tree',
        field : 'name',
        width : 280,
        text  : 'Resource / Task',
        renderer({ record, grid }) {
            // Unwrap record to its origin - resource or assignment
            record = grid.resolveRecordToOrigin(record);

            if (record.key?.isResourceModel) {
                record = record.key;
            }

            // If that's a resource row
            if (record.isResourceModel) {
                if (!this.avatarRendering) {
                    this.avatarRendering = new AvatarRendering({
                        element : grid.element
                    });
                }

                return {
                    class    : 'b-resource-info',
                    children : [
                        this.avatarRendering.getResourceAvatar({
                            initials : record.initials,
                            color    : record.eventColor,
                            iconCls  : record.iconCls,
                            imageUrl : record.image ? `${grid.resourceImageFolderPath}${record.image}` : null
                        }),
                        record.name
                    ]
                };
            }
            // If that's an assignment row
            else if (record.isAssignmentModel) {
                return StringHelper.encodeHtml(record.event?.name);
            }

            // Otherwise record represents a group
            // so record.key might have: resource, event or city
            return record.key?.name || record.key;
        }
    },
    {
        text    : 'Task date range',
        cellCls : 'taskDateRange',
        renderer({ record, grid }) {
            record = grid.resolveRecordToOrigin(record);

            // Show event start/end for assignment row
            if (record.isAssignmentModel) {
                const task = record.event;

                return DateHelper.format(task.startDate, 'MMM Do') + ' - ' + DateHelper.format(task.endDate, 'MMM Do');
            }

            return '';
        }
    }
],

tbar : {
    cls   : 'utilization-toolbar',
    items : [
        {
            type     : 'checkbox',
            ref      : 'showBarTip',
            text     : 'Enable bar tooltip',
            tooltip  : 'Check to show tooltips when moving mouse over bars',
            checked  : true,
            onAction : 'up.onShowBarTipToggle'
        },
        {
            ref      : 'showTimeLeft',
            type     : 'checkbox',
            text     : 'Show time left',
            tooltip  : 'Check to show time left in bars',
            onAction : 'up.onShowTimeLeftToggle'
        },
        '->',
        {
            type : 'label',
            text : 'Group by'
        },
        {
            type        : 'buttongroup',
            toggleGroup : true,
            cls         : 'group-buttons',
            items       : [
                {
                    text                 : 'Resource, Project',
                    tooltip              : 'Click to toggle Resource-Project to Project-Resource grouping',
                    icon                 : 'b-fa b-fa-arrow-down',
                    pressed              : true,
                    supportsPressedClick : true,
                    onAction() {
                        // toggle group direction for this button
                        this._groupDirection = !this._groupDirection;

                        if (this._groupDirection) {
                            this.icon = 'b-fa b-fa-arrow-up';

                            // group in backward order - first by Project and then by Resource
                            resourceUtilization.group([...resourceNProjectGroupFns].reverse());
                        }
                        else {
                            this.icon = 'b-fa b-fa-arrow-down';

                            resourceUtilization.group(resourceNProjectGroupFns);
                        }
                    }
                },
                {
                    text    : 'City, Resource',
                    tooltip : 'Group by City and Resource',
                    onAction() {
                        resourceUtilization.group([
                            // by city
                            ({ origin }) => origin.isResourceModel ? origin.city : origin.resource.city,
                            // Second group by resource ..if that's an unassigned resource just stop grouping
                            ({ origin }) => origin.isResourceModel ? Store.StopBranch : origin.resource
                        ]);
                    }
                },
                {
                    text    : 'Default',
                    tooltip : 'Reset grouping to the default state',
                    onAction() {
                        // reset grouping feature - back to default view
                        resourceUtilization.clearGroups();
                    }
                }
            ]
        }
    ]
},

onShowBarTipToggle({ source }) {
    resourceUtilization.showBarTip = source.checked;
},

onShowTimeLeftToggle({ source }) {
    resourceUtilization.showLeftTime = source.checked;

    // schedule the view refresh
    resourceUtilization.scheduleRefreshRows();
}
});

Post by ghulam.ghous »

The late calendar has start date of march 25, 2025 but your gantt data spans over jan and feb in 2024. So first of all please correct the data, secondly what is your concept of blocked? I have shared with you it is not gonna show anythin visually but it will impact the calculations. See the code pen below: https://codepen.io/ghulamghousdev/pen/qEBGaoY. See here how the task in the gantt has got it's duration affected by the calendar. Also nothing is rendered on the resource utilization widget on those dates.


Post by rocketreading »

I defined the server side controller in a way that resources are coming like below.
Here is the sample of one of the resource.
So this should work right? I believe it is not working for me.

{
  "id": "0Hn8d000000TtPfCAK",
  "name": "Nathan Jackson",
  "calendar": {
    "id": "calendar-0Hn8d000000TtPfCAK",
    "name": "Nathan Jackson Schedule",
    "intervals": [
      {
        "recurrentStartDate": "on Sat at 00:00",
        "recurrentEndDate": "on Mon at 00:00",
        "isWorking": false
      },
      {
        "startDate": "2022-12-27",
        "endDate": "2022-12-28",
        "isWorking": false,
        "name": "Boxing Day 2022",
        "cls": "holiday"
      },
      {
        "startDate": "2022-12-26",
        "endDate": "2022-12-27",
        "isWorking": false,
        "name": "Christmas Day 2022",
        "cls": "holiday"
      },
      // ... (other interval entries) ...
      {
        "startDate": "2026-12-28",
        "endDate": "2026-12-29",
        "isWorking": false,
        "name": "Boxing Day 2026",
        "cls": "holiday"
      },
      {
      "startDate": "2024-09-27", 
      "endDate": "2024-10-01", 
      "isWorking": false, 
      "name": "Vacation", 
      "cls": "absence"
      }
    ]
  }
}

Post by ghulam.ghous »

Hey there,

This is not the correct way. You should define calendars in the calendars part and use ids in the resources data to make it work. So the above should be:


"calendars" : { "rows" : {
    "id": "calendar-0Hn8d000000TtPfCAK",
    "name": "Nathan Jackson Schedule",
    "intervals": [
      {
        "recurrentStartDate": "on Sat at 00:00",
        "recurrentEndDate": "on Mon at 00:00",
        "isWorking": false
      },
      {
        "startDate": "2022-12-27",
        "endDate": "2022-12-28",
        "isWorking": false,
        "name": "Boxing Day 2022",
        "cls": "holiday"
      },
      {
        "startDate": "2022-12-26",
        "endDate": "2022-12-27",
        "isWorking": false,
        "name": "Christmas Day 2022",
        "cls": "holiday"
      },
      // ... (other interval entries) ...
      {
        "startDate": "2026-12-28",
        "endDate": "2026-12-29",
        "isWorking": false,
        "name": "Boxing Day 2026",
        "cls": "holiday"
      },
      {
      "startDate": "2024-09-27", 
      "endDate": "2024-10-01", 
      "isWorking": false, 
      "name": "Vacation", 
      "cls": "absence"
      }
    ]
  }}},

"resources : { "rows" : [{
  "id": "0Hn8d000000TtPfCAK",
  "name": "Nathan Jackson",
  "calendar": "calendar-0Hn8d000000TtPfCAK"
}]}

Post Reply