Our state of the art Gantt chart


Post by jeanbaptiste.minani »

Dear Bryntum support team,

I got the error "Cannot read properties of undefined (reading 'startDate')" after migrating from Gantt v4.1.2 to the latest version v5.3.5. I've attached the screenshot for more details. The error displays when you attempt to edit the start or end dates by clicking on them. What might cause the error and how can it be resolved?

Attachments
gantt error - start-end dates edit.PNG
gantt error - start-end dates edit.PNG (60.54 KiB) Viewed 847 times

Post by tasnim »

Hi,

I tried to reproduce the problem with our advanced demo https://bryntum.com/products/gantt/examples/advanced/
But not able to reproduce.

Would you be able to provide a sample test case here so we can download and run it? And see what's wrong?


Post by jeanbaptiste.minani »

Below is the sample code

import Gantt from '../build-gantt/lib/Gantt/view/Gantt.js';
import TaskModel from '../build-gantt/lib/Gantt/model/TaskModel.js';
import ProjectModel from '../build-gantt/lib/Gantt/model/ProjectModel.js';
import Toast from '../build-gantt/lib/Core/widget/Toast.js';
import DateHelper from '../build-gantt/lib/Core/helper/DateHelper.js';
import StringHelper from '../build-gantt/lib/Core/helper/StringHelper.js';
import '../build-gantt/lib/Gantt/column/AllColumns.js';
import Column from '../build-gantt/lib/Grid/column/Column.js';
import ColumnStore from '../build-gantt/lib/Grid/data/ColumnStore.js';
import LocaleManager from '../build-gantt/lib/Core/localization/LocaleManager.js';
import Localizable from '../build-gantt/lib/Core/localization/Localizable.js';
import '../build-gantt/lib/Scheduler/feature/TimeRanges.js';
import '../build-gantt/lib/Gantt/feature/Baselines.js';
import '../build-gantt/lib/SchedulerPro/feature/DependencyEdit.js';
import '../build-gantt/lib/Grid/feature/Filter.js';
import '../build-gantt/lib/Gantt/feature/Labels.js';
import '../build-gantt/lib/Gantt/feature/ProjectLines.js';
import '../build-gantt/lib/Gantt/feature/ProgressLine.js';
import '../build-gantt/lib/Gantt/feature/Rollups.js';
import '../build-gantt/lib/Gantt/feature/TaskEdit.js';
import '../build-gantt/lib/Gantt/feature/export/PdfExport.js';
import '../build-gantt/lib/Grid/feature/experimental/ExcelExporter.js';
import '../build-gantt/lib/Core/widget/ButtonGroup.js';
import '../build-gantt/lib/Scheduler/widget/UndoRedo.js';


LocaleManager.applyLocale("En");


// region Label configs
const
    topLabel    = {
        field  : 'name',
        editor : {
            type : 'textfield'
        }
    },
    bottomLabel = {
        field    : 'startDate',
        renderer : function({ taskRecord }) {
            return DateHelper.format(taskRecord.startDate, 'DD-MMM-Y');
        }
    };

// endregion

// using module bundle
LocaleManager.locale = userLocale;

// also possible to reach it from the gantt instance
//gantt.localeManager.locale = 'Fr';


const headerTpl = ({currentPage, totalPages}) => `
    <div class="demo-export-header">
        <img src="/assets/img/logo.png" />
        <dl>
            <dt>Date: ${DateHelper.format(new Date(), 'll LT')}</dt>
            <dd>${(currentPage && totalPages) ? `Page: ${currentPage + 1}/${totalPages}` : ''}</dd>
        </dl>
    </div>`;


const footerTpl = () => '<div class="demo-export-footer"><h3>© 2020 PMS</h3></div>';

class StatusColumn extends Column {
    static get $name() {
        return 'StatusColumn';
    }

static get type() {
    return 'statuscolumn';
}

static get isGanttColumn() {
    return true;
}

static get defaults() {
    return {
        // Set your default instance config properties here
        field      : 'status',
        text       : 'Status',
        editor     : false,
        cellCls    : 'b-status-column-cell',
        htmlEncode : false,
        filterable : {
            filterField : {
                type  : 'combo',
                items : ['Not Started', 'Started', 'Completed', 'Late']
            }
        }
    };
}

renderer({ isExport }) {

}

//endregion
renderer({ record ,isExport}) {
    const status = record.status;

    if (isExport) {
        return record.status;
    }
    else {
        // widget rendering routine
        return status ? {
            tag       : 'i',
            className : `b-fa b-fa-circle ${status}`,
            html      : status
        } : '';
    }


}
}

ColumnStore.registerColumnType(StatusColumn);

class MyTaskModel extends TaskModel {

static get fields() {
    return [
        {
            name : 'weight',
            type : 'number',
            convert(value, record) {
                // call standard number field convert() method
                value = this.constructor.prototype.convert.call(this, value, record);

                value = value ? Number(value) : 0;

                if(typeof(record) != "undefined") {
                    const parent = record.parent;

                    // sum up total weight for children
                    if (parent) {
                        let sum = value;

                        parent.children.forEach(task => {
                            if (task !== record) {
                                sum += task.weight;
                            }
                        });

                        // reduce if it's >= 100
                        if (sum > 100) {
                            value -= sum - 100;
                        }
                    }
                }
                return value;
            }
        }
    ]
}


* calculatePercentDone(proposedValue) {
    // if the event has sub-events
    if (yield * this.hasSubEvents()) {
        let parentWeight = 0;

        // get sub-events
        const subEvents = yield * this.subEventsIterable();

        for (const subEvent of subEvents) {
            // get the sub-event percentDone
            const percentDone = yield subEvent.$.percentDone;

            parentWeight += subEvent.weight * percentDone / 100;
        }

        return parentWeight;
    }

    // for leaf tasks use default logic
    return yield * super.calculatePercentDone(proposedValue);
}

get isLate() {
    return this.deadline && Date.now() > this.deadline;
}

get status() {
    let status = 'Not started';

    if (this.isCompleted) {
        status = 'Completed';
    }
    else if (this.endDate < Date.now() || (this.startDate<Date.now() && !this.isStarted)) {
        status = 'Late';
    }
    else if (this.isStarted) {
        status = 'Started';
    }

    return status;
}
}

function setBaseline(index) {
    gantt.taskStore.setBaseline(index);
}

function toggleBaselineVisible(index, visible) {
    //gantt.element.classList[visible ? 'remove' : 'add'](`b-hide-baseline-${index}`);
    console.log(`baseline-${index}`);
    var elements=document.getElementsByClassName(`baseline-${index}`);
    for (let i = 0; i < elements.length; i++) {
        elements[i].classList[visible ? 'remove' : 'add'](`hide-baseline`);
    }

}

let project = new ProjectModel({
    taskModelClass: MyTaskModel,
    autoSync: true,
    transport: {
        load: {
            url: ""
            //url: '../assets/build-gantt/_datasets/launch-saas.json'
        },
        sync: {
            url: ""
        }
    },
    autoLoad: true,
    autoCalculatePercentDoneForParentTasks: false,

// The State TrackingManager which the UndoRedo widget in the toolbar uses
stm: {
    autoRecord: true
},

});


let baselines = JSON.parse(baselinesStr);
let baselinesList=[];
let index=1;

baselines.forEach(function(item){
    console.log("Baselines:");
    console.log(item);
    baselinesList.push(
        {
            checked: true,
            text: item.name,
            onToggle({checked}) {
                toggleBaselineVisible(item.id, checked);
            }
        }
    );
});


const readOnly = (projectStatus === STATUS_COMPLETED || projectStatus === STATUS_FAILED || userRole===ROLE_NORMAL) ? true : false;
const hideContextMenu=(projectStatus === STATUS_COMPLETED || projectStatus === STATUS_FAILED || userRole===ROLE_NORMAL || projectStatus === STATUS_ON_TRACK || projectStatus === STATUS_DELAYED) ? true : false;

function isFieldReadOnly(field) {
    var readOnly = false;
    switch (field) {
        case "percentdone":
            readOnly = (projectStatus === STATUS_PLANNING || projectStatus === STATUS_ON_HOLD || projectStatus === STATUS_REJECTED);
            break;
        case "":
            // code block
            break;
        default:
            readOnly = (projectStatus === STATUS_ON_TRACK || projectStatus === STATUS_DELAYED);
        // code block

}
return readOnly;

}

const apiRequestAction = async (method, url, headers, body) => {
    let fetchObject = {
        method,
        headers: { 'Content-Type': 'application/json', ...headers }
    };
    if (body) fetchObject = { ...fetchObject, body: JSON.stringify(body) };

const response = await fetch(url, { ...fetchObject });
return await response.json();
};

let gantt = new Gantt({
    project: project,
    readOnly: readOnly,
    startDate: new Date(projectStartDate),
    endDate: new Date(projectEndDate),

columns: [
    {type: 'sequence',text: '#' },
    {type: 'wbs',
        renderer({ value }) {
            return String(value);
        }
    },
    {type: 'name', field: 'name', text: Localizable().L('L{NameColumn.Name}'), editor: !isFieldReadOnly("name"), filterable: true },
    {
        type: 'number',
        min: 0,
        max: 100,
        text:Localizable().L('Weight'),
        align: 'center',
        field: 'weight',
        editor: !isFieldReadOnly("weight"),
        filterable: true
    },
    {type: 'startdate', text: Localizable().L('L{StartDateColumn.Start}'), editor: !isFieldReadOnly("startdate"), filterable: true,min: projectStartDate,max: projectEndDate},
    { type:'enddate', text: Localizable().L('L{EndDateColumn.Finish}'), editor: !isFieldReadOnly("enddate"), filterable: true,min: projectStartDate,max: projectEndDate },
    {type: 'duration', text: Localizable().L('L{DurationColumn.Duration}'), editor: !isFieldReadOnly("duration"), filterable: true },
    { type : 'statuscolumn', filterable: true},

    {type: 'resourceassignment', width: 120, showAvatars: true, editor: !isFieldReadOnly("resourceassignment"), filterable: true },
    {
        type: 'percentdone',
        text: Localizable().L('L{PercentDoneColumn.% Done}'),
        showCircle: true,
        width: 70,
        editor: !isFieldReadOnly("percentdone"),
        filterable: true,
        renderer({ record, isExport }) {
            const value = record.renderedPercentDone;

            if (this.showCircle && !isExport) {
                return {
                    className : {
                        'b-percentdone-circle' : 1,
                        'b-full'               : value === 100,
                        'b-empty'              : value === 0
                    },
                    style     : {
                        height                      : this.circleHeightPercentage * this.grid.rowHeight + 'px',
                        width                       : this.circleHeightPercentage * this.grid.rowHeight + 'px',
                        '--gantt-percentdone-angle' : `${value / 100}turn`
                    },
                    dataset : {
                        value
                    }
                };

            }

            return value;
        }
    },
    {type: 'predecessor', editor: !isFieldReadOnly("predecessor"), filterable: true },
    {type: 'successor', editor: !isFieldReadOnly("successor"), filterable: true },
    //{ type : 'milestone' },

    //{ type : 'schedulingmodecolumn' },
    {type : 'calendar'},
    //{type: 'constrainttype', editor: !isFieldReadOnly("constrainttype")},
    //{type: 'constraintdate', editor: !isFieldReadOnly("constraintdate")},
    //{type: 'date', text: 'Deadline', field: 'deadline', editor: !isFieldReadOnly("deadline"), filterable: true },

    //{type: 'date', text: 'Finish', field: 'enddate', editor: !isFieldReadOnly("finish")},

    { type: 'addnew', field: 'addnew', text: 'Addnew' },

],

style: 'font-size:0.85em',
autoHeight: true,
subGridConfigs: {
    locked: {flex: 3},
    normal: {flex: 4}
},
listeners: {
    beforeCellEditStart({editorContext}) {
        return Boolean(editorContext.record.parentId);
    },
    beforeTaskEdit({ taskRecord, taskElement }) {

        if(!taskRecord.parentId)
        {
            // Prevent built in editor
            return false;
        }


    },
    beforeTaskEditShow({ editor, taskRecord }) {


        console.log(editor.widgetMap);
        editor.widgetMap.constraintDateField.hidden=true;
        editor.widgetMap.constraintTypeField.hidden=true;
        editor.widgetMap.manuallyScheduledField.hidden=true;
        editor.widgetMap.rollupField.hidden=true;
        editor.widgetMap.divider.visible=false;


    },
    paint : ({ source }) => {
        // find "Add New..." column
        // const addNewColumn = source.columns.find(col => col.isAddNewColumn);
        //console.log(addNewColumn.columnCombo.store);

        // filter the column drop down list
        //addNewColumn.columnCombo.store.addFilter
        source.columns.findRecord('type', 'addnew').combo.store.addFilter({
            id       : 'my-filter',
            // define filter function that will skip columns of certain types
            filterBy : (column) => {
                return column.id == 'earlystartdate' || column.id == 'earlyenddate' || column.id == 'lateenddate' || column.id == 'latestartdate' || column.id == 'effort' || column.id == 'totalslack' || column.id == 'milestone' || column.id == 'note' || column.id == 'schedulingmode';
            }
        });
    }
},
features: {
    criticalPaths: true,
    cellEdit: {
        addNewAtEnd: false,
    },

    rowReorder : {
        listeners : {
            gridRowBeforeDropFinalize({ context }) {
                return Boolean(context.insertBefore.parentId);
            }

        }
    },

    filter: true,
    taskEdit: {
        disabled : hideContextMenu,
        items: {
            generalTab: {
                items: {
                    name: { readOnly: isFieldReadOnly("name") },
                    startDate: { readOnly: isFieldReadOnly("startdate") },
                    endDate: { readOnly: isFieldReadOnly("enddate") },
                    duration: { readOnly: isFieldReadOnly("duration") },
                    statusColumn: { readOnly: isFieldReadOnly('statuscolumn') },
                    percentDone: { readOnly: isFieldReadOnly("percentDone") },
                    effort: { readOnly: isFieldReadOnly("effort") },
                    deadLine: { readOnly: isFieldReadOnly("deadline") },
                    newGeneralField: {
                        type: 'number',
                        weight: 710,
                        label: 'Weight',
                        name: 'weight',
                        min: 0,
                        max: 100,
                        readOnly: isFieldReadOnly('weight')
                    }
                }
            },
            resourcesTab: {
                items: {
                    resourceassignment: { readOnly: isFieldReadOnly("resourceassignment") },
                }
            },
            advancedTab: {
                items: {
                    constraintType: false,
                    constraintDate: false,
                    divider     : false
                }
            }
        }
    },

    taskMenu: {
        processItems({items, taskRecord}) {
            const readOnly = !taskRecord.parentId;
            console.log(items);
            items.add.menu.addTaskAbove.hidden =
                items.add.menu.addTaskBelow.hidden =
                    items.editTask.hidden =
                        items.deleteTask.hidden =
                            readOnly;
            //items.add.menu.subtask.hidden = !readOnly;
            items.add.menu.successor.hidden=readOnly;
            items.add.menu.predecessor.hidden=readOnly;
            items.add.menu.milestone.hidden=readOnly;

            items.convertToMilestone.hidden=readOnly;

            //items.add.menu.filter.hidden=readOnly;
            items.indent.hidden=readOnly;
            items.outdent.hidden=readOnly;
            if(typeof(items.filterStringEquals) != "undefined") {
                items.filterStringEquals.hidden=readOnly;
            }
            console.log("Level : "+taskRecord.childLevel);
            if(taskRecord.childLevel==1){
                items.outdent.hidden=true;
            }

            console.log(hideContextMenu);
            if(hideContextMenu){
                return false;
            }

            if(!readOnly){

                items.add.menu.addTaskAbove.onItem=async ({taskRecord}) => {
                    const task = await gantt.addTaskAbove(taskRecord);
                    task.name = 'New task';
                    task.duration = 2;
                    task.calendar=gantt.project.calendar.id;
                    gantt.project.commitAsync();
                }

                items.add.menu.addTaskBelow.onItem=async ({taskRecord}) => {
                    const task = await gantt.addTaskBelow(taskRecord);
                    task.name = 'New task';
                    task.duration = 2;
                    task.calendar=gantt.project.calendar.id;
                    gantt.project.commitAsync();
                }

            }

            items.add.menu.subtask.onItem=async ({taskRecord}) => {
                const task = await gantt.addSubtask(taskRecord);
                task.name = 'New subtask';
                task.duration = 2;
                console.log(gantt.project.calendar.id);
                task.calendar=gantt.project.calendar.id;
                gantt.project.commitAsync();

                console.log(task.duration);
            }

        },

    },

    // baselines : true,
    baselines: {
        // Custom tooltip template for baselines
        template(data) {
            const
                me = this,
                {baseline} = data,
                {task} = baseline,
                delayed = task.startDate > baseline.startDate,
                overrun = task.durationMS > baseline.durationMS;

            console.log(baseline);

            let {decimalPrecision} = me;

            if (decimalPrecision == null) {
                decimalPrecision = me.client.durationDisplayPrecision;
            }

            const
                multiplier = Math.pow(10, decimalPrecision),
                displayDuration = Math.round(baseline.duration * multiplier) / multiplier;

            return `
                <div class="b-gantt-task-title">${StringHelper.encodeHtml(task.name)} (${baseline.name})</div>
                <table>
                <tr><td>${me.L('Start')}:</td><td>${data.startClockHtml}</td></tr>
                ${baseline.milestone ? '' : `
                    <tr><td>${me.L('End')}:</td><td>${data.endClockHtml}</td></tr>
                    <tr><td>${me.L('Duration')}:</td><td class="b-right">${displayDuration + ' ' + DateHelper.getLocalizedNameOfUnit(baseline.durationUnit, baseline.duration !== 1)}</td></tr>
                `}
                </table>
                ${delayed ? `
                    <h4 class="statusmessage b-baseline-delay"><i class="statusicon b-fa b-fa-exclamation-triangle"></i>${me.L('Delayed start by')} ${DateHelper.formatDelta(task.startDate - baseline.startDate)}</h4>
                ` : ''}
                ${overrun ? `
                    <h4 class="statusmessage b-baseline-overrun"><i class="statusicon b-fa b-fa-exclamation-triangle"></i>${me.L('Overrun by')} ${DateHelper.formatDelta(task.durationMS - baseline.durationMS)}</h4>
                ` : ''}
                `;
        }
    },
    pdfExport: {
        exportServer: 'http://localhost:8080', //Print server
        translateURLsToAbsolute : 'http://localhost:8080/resources', //Print server
        clientURL : 'http://localhost:8080', //App server
        headerTpl,
        footerTpl
    },
    excelExporter: {
        // Choose the date format for date fields
        dateFormat: 'YYYY-MM-DD HH:mm'
    },
    /*labels : {
        top: topLabel,
        bottom: null,
        left: null,
        right: null,
       labelCls: 'label-css'
    }*/
},
dependencyIdField: 'wbsCode',
tbar: {
    items: [
        {
            type: 'buttonGroup',
            items: [
                /*{
                    type: 'button',
                    text: 'Add Task',

                    onAction() {
                        // console.log(project.taskStore.first.appendChild({
                            name: 'New Task', duration: '1'
                        }));
                    }
                },*/
                {
                    color: 'b-green',
                    ref: 'addTaskButton',
                    icon: 'b-fa b-fa-plus',
                    text: 'Create',
                    tooltip: 'Create new task',
                    style: 'margin-right: .5em',
                    disabled: readOnly || isFieldReadOnly("name"),
                    async onAction() { //onAddTaskClick

                        const newTask = {name: 'New task', duration: '2', calendar: gantt.project.calendar.id};
                        console.log("From create task:");
                        console.log(gantt.project.calendar.id);

                        //const added = gantt.taskStore.rootNode.appendChild(newTask);
                        let added =project.taskStore.first.appendChild({
                            name: 'New Task', duration: '2',weight:'0',calendar: gantt.project.calendar.id
                        });

                        // run propagation to calculate new task fields
                        // await project.comitAsync();

                        // run propagation to calculate new task fields
                        await gantt.project.propagateAsync();


                        // scroll to the added task. Smoothly.
                        await gantt.scrollRowIntoView(added, {
                            animate: true
                        });

                        gantt.features.cellEdit.startEditing({
                            record: added,
                            field: 'name'
                        });
                        Toast.show('Created a new task, you may update it at the same or click somewhere else to avoid update');
                        /*Toast.show({
                            html: 'Created a new task, you may update it at the same or click somewhere else to avoid update',
                            color: 'b-light-green'
                        });*/
                    }
                }
            ]
        },
        {
            type: 'buttonGroup',
            items: [
                {
                    color: 'b-green',
                    icon: 'b-fa b-fa-pen',
                    text: 'Edit',
                    tooltip: 'Edit selected task',
                    disabled: readOnly || isFieldReadOnly("name"),
                    onAction() { //onEditTaskClick
                        if (gantt.selectedRecord && gantt.selectedRecord.parentId) {
                            gantt.editTask(gantt.selectedRecord);
                        }else if (gantt.selectedRecord && !gantt.selectedRecord.parentId) {
                            Toast.show('Sorry, you can not edit project task');
                        }else {
                            Toast.show('First select the task you want to edit');
                        }
                    }
                }
            ]
        },
        {
            ref: 'undoRedo',
            type: 'undoredo',
            items: {
                transactionsCombo: null
            }
        },
        {
            type: 'buttonGroup',
            items: [
                {
                    ref: 'expandAllButton',
                    icon: 'b-fa b-fa-angle-double-down',
                    tooltip: 'Expand all',
                    onAction() { //onExpandAllClick
                        gantt.expandAll();
                    }
                },
                {
                    ref: 'collapseAllButton',
                    icon: 'b-fa b-fa-angle-double-up',
                    tooltip: 'Collapse all',
                    onAction() { //onCollapseAllClick
                        gantt.collapseAll();
                    }
                }
            ]
        },
        {
            type: 'buttonGroup',
            items: [
                {
                    ref: 'zoomInButton',
                    icon: 'b-fa b-fa-search-plus',
                    tooltip: 'Zoom in',
                    onAction() { //onZoomInClick
                        gantt.zoomIn();
                    }
                },
                {
                    ref: 'zoomOutButton',
                    icon: 'b-fa b-fa-search-minus',
                    tooltip: 'Zoom out',
                    onAction() { //onZoomOutClick
                        gantt.zoomOut();
                    }
                },
                {
                    ref: 'zoomToFitButton',
                    icon: 'b-fa b-fa-compress-arrows-alt',
                    tooltip: 'Zoom to fit',
                    onAction() {
                        gantt.zoomToFit({ //onZoomToFitClick
                            leftMargin: 50,
                            rightMargin: 50
                        });
                    }
                },
                {
                    ref: 'previousButton',
                    icon: 'b-fa b-fa-angle-left',
                    tooltip: 'Previous time span',
                    onAction() { //'up.onShiftPreviousClick'
                        gantt.shiftPrevious();
                    }
                },
                {
                    ref: 'nextButton',
                    icon: 'b-fa b-fa-angle-right',
                    tooltip: 'Next time span',
                    onAction() { //'up.onShiftNextClick'
                        gantt.shiftNext();
                    }
                }
            ]
        },



        {
            type: 'buttonGroup',
            items: [
                {
                    type: 'button',
                    text: 'Export to Excel',
                    ref: 'excelExportBtn',
                    icon: 'b-fa-file-export',
                    onAction: () => {
                        const filename = gantt.project.taskStore.first && gantt.project.taskStore.first.name;
                        gantt.features.excelExporter.export({
                            filename
                        });
                    }
                },

                {
                    type : 'button',
                    ref  : 'exportButton',
                    icon : 'b-fa-file-export',
                    text : 'Export to PDF',
                    onClick() {
                        gantt.zoomToFit({ //onZoomToFitClick
                            leftMargin: 50,
                            rightMargin: 50
                        });
                        gantt.features.pdfExport.showExportDialog();
                    }
                }
            ]
        },
        '->',
        {
            type      : 'combo',
            ref       : 'calendar',
            label     : 'Default calendar',
            items : [{ value: 'general', text: 'General' }, { value: 'business', text: 'Business' }],
            flex      : '0 0 17em',
            text : 'Default calendar description',
            readOnly:true,
            onChange:({ value, oldValue }) => {
                if (!oldValue) {
                    return;
                }

                console.log(value);


                apiRequestAction('POST', '', {}, {"projectId":projectId,"calendar":value})
                    .then(res => {
                        //loadSpinner(false);
                        console.log('res => ', res);
                    })
                    .catch(err => {
                        //loadSpinner(false);
                        console.log('err => ', err);
                    });                    

                if(gantt.project.taskStore.first){                      
                }
                console.log("From change calendar:");
                console.log(gantt.project.getCalendar());
            }
        },

        '->',

        {
            type: 'textfield',
            ref: 'filterByName',
            cls: 'filter-by-name',
            flex: '0 0 12.5em',
            // Label used for material, hidden in other themes
            label: 'Find tasks by name',
            // Placeholder for others
            placeholder          : 'Find tasks by name',
            clearable: true,
            keyStrokeChangeDelay: 100,
            triggers: {
                filter: {
                    align: 'end',
                    cls: 'b-fa b-fa-filter'
                }
            },
            onChange({value}) { //'up.onFilterChange'
                if (value === '') {
                    gantt.taskStore.clearFilters();
                } else {
                    value = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

                    gantt.taskStore.filter({
                        filters: task => task.name && task.name.match(new RegExp(value, 'i')),
                        replace: true
                    });
                }
            }
        },

        {
            type: 'buttonGroup',
            items: [
                {
                    type: 'button',
                    text: 'Show baseline',
                    icon: 'b-fa-bars',
                    iconAlign: 'end',
                    menu: baselinesList
                }
            ]
        },
        {
            type: 'checkbox',
            text: 'Show baselines',
            checked: true,
            ref:'showBaselines',
            toggleable: true,
            onAction({checked}) {
                gantt.features.baselines.disabled = !checked;
            }
        },

        {
            type: 'button',
            ref: 'featuresButton',
            icon: 'b-fa b-fa-tasks',
            text: 'Settings',
            tooltip: 'Toggle features',
            toggleable: true,
            menu: {
                onItem({source: item}) { //'up.onFeaturesClick',
                    if (item.feature) {
                        const feature = gantt.features[item.feature];
                        feature.disabled = !feature.disabled;
                    } else if (item.subGrid) {
                        const subGrid = gantt.subGrids[item.subGrid];
                        subGrid.collapsed = !subGrid.collapsed;
                    }
                },
                onBeforeShow({source: menu}) { //'up.onFeaturesShow',
                    menu.items.map(item => {
                        const {feature} = item;

                        if (feature) {
                            // a feature might be not presented in the gantt
                            // (the code is shared between "advanced" and "php" demos which use a bit different set of features)
                            if (gantt.features[feature]) {
                                item.checked = !gantt.features[feature].disabled;
                            }
                            // hide not existing features
                            else {
                                item.hide();
                            }
                        } else if (item.subGrid) {
                            item.checked = gantt.subGrids[item.subGrid].collapsed;
                        }
                    });
                },

                // "checked" is set to a boolean value to display a checkbox for menu items. No matter if it is true or false.
                // The real value is set dynamically depending on the "disabled" config of the feature it is bound to.
                items: [
                    {
                        text: 'UI settings',
                        icon: 'b-fa-sliders-h',
                        menu: {
                            type: 'popup',
                            anchor: true,
                            cls: 'settings-menu',
                            layoutStyle: {
                                flexDirection: 'column'
                            },
                            onBeforeShow({source: menu}) { //'up.onSettingsShow',
                                const {rowHeight, barMargin, duration} = menu.widgetMap;

                                rowHeight.value = gantt.rowHeight;
                                barMargin.value = gantt.barMargin;
                                barMargin.max = (gantt.rowHeight / 2) - 5;
                                duration.value = gantt.transitionDuration;
                            },

                            items: [
                                {
                                    type: 'slider',
                                    ref: 'rowHeight',
                                    text: 'Row height',
                                    width: '12em',
                                    showValue: true,
                                    min: 30,
                                    max: 70,
                                    onInput({value, source}) { //'up.onRowHeightChange'
                                        gantt.rowHeight = value;
                                        source.owner.widgetMap.barMargin.max = (value / 2) - 5;
                                    }
                                },
                                {
                                    type: 'slider',
                                    ref: 'barMargin',
                                    text: 'Bar margin',
                                    width: '12em',
                                    showValue: true,
                                    min: 0,
                                    max: 10,
                                    onInput({value}) { //'up.onBarMarginChange'
                                        gantt.barMargin = value;
                                    }
                                },
                                {
                                    type: 'slider',
                                    ref: 'duration',
                                    text: 'Animation duration ',
                                    width: '12em',
                                    min: 0,
                                    max: 2000,
                                    step: 100,
                                    showValue: true,
                                    onInput({value}) { //'up.onAnimationDurationChange'
                                        gantt.transitionDuration = value;
                                        styleNode.innerHTML = `.b-animating .b-gantt-task-wrap { transition-duration: ${value / 1000}s !important; }`;
                                    }
                                }
                            ]
                        }
                    },
                    {
                        text: 'Draw dependencies',
                        feature: 'dependencies',
                        checked: false
                    },
                    {
                        text: 'Task labels',
                        feature: 'labels',
                        checked: false
                    },
                    {
                        text: 'Critical paths',
                        feature: 'criticalPaths',
                        tooltip: 'Highlight critical paths',
                        checked: false
                    },
                    {
                        text: 'Project lines',
                        feature: 'projectLines',
                        checked: false
                    },
                    {
                        text: 'Highlight non-working time',
                        feature: 'nonWorkingTime',
                        checked: false
                    },
                    /* {
                        text: 'Enable cell editing',
                        feature: 'cellEdit',
                        checked: false
                    }, */
                    /* {
                        text: 'Show baselines',
                        feature: 'baselines',
                        checked: false
                    }, */
                    {
                        text: 'Show rollups',
                        feature: 'rollups',
                        checked: false
                    },
                    {
                        text: 'Show progress line',
                        feature: 'progressLine',
                        checked: false
                    },
                    {
                        text: 'Hide schedule',
                        cls: 'b-separator',
                        subGrid: 'normal',
                        checked: false
                    }
                ]
            }
        }
    ]
},
appendTo: document.getElementById('granttId')
});




/*gantt.taskStore.on({
    catchAll(event) {
        if(event.type ==='add'){
            console.log('Event Listener catch All (ADD TASK)....', event.type, event)

        console.log(event.records[0]);
    }
}
})*/

gantt.taskStore.on({
    add:(event) => {
        console.log('TaskStore Event ADD', event.records[0]);

    let record=event.records[0];
    let value = record.weight;

    value = value ? Number(value) : 0;

    if(typeof(record) != "undefined") {
        const parent = record.parent;

        // sum up total weight for children
        if (parent) {
            let sum = value;

            parent.children.forEach(task => {
                if (task !== record) {
                    sum += task.weight;
                }
            });

            // reduce if it's >= 100
            if (sum > 100) {
                value -= sum - 100;
            }
        }
    }

    record.weight=value;

},
});

gantt.project.on({
    load:(event) => {
        updateProjectCalendar();
    }
});

function updateProjectCalendar(){
    console.log(gantt.project.calendar);
    gantt.tbar.widgetMap.calendar.value=gantt.project.calendar.id;
}
gantt.tbar.widgetMap.showBaselines.checked=false;

Post by jeanbaptiste.minani »

Hello Bryntum team,

Any update on this issue?


Post by marcio »

Hello jeanbaptiste.minani,

I tried to run your snippet but got some missing variables/parameters, are you able to share a runnable test case?

Best regards,
Márcio


Post by jeanbaptiste.minani »

Bellow is a runnable test case:

import Gantt from '../build-gantt/lib/Gantt/view/Gantt.js';
import TaskModel from '../build-gantt/lib/Gantt/model/TaskModel.js';
import ProjectModel from '../build-gantt/lib/Gantt/model/ProjectModel.js';
import Toast from '../build-gantt/lib/Core/widget/Toast.js';
import DateHelper from '../build-gantt/lib/Core/helper/DateHelper.js';
import StringHelper from '../build-gantt/lib/Core/helper/StringHelper.js';
import '../build-gantt/lib/Gantt/column/AllColumns.js';
import Column from '../build-gantt/lib/Grid/column/Column.js';
import ColumnStore from '../build-gantt/lib/Grid/data/ColumnStore.js';
import LocaleManager from '../build-gantt/lib/Core/localization/LocaleManager.js';
import Localizable from '../build-gantt/lib/Core/localization/Localizable.js';
import '../build-gantt/lib/Scheduler/feature/TimeRanges.js';
import '../build-gantt/lib/Gantt/feature/Baselines.js';
import '../build-gantt/lib/SchedulerPro/feature/DependencyEdit.js';
import '../build-gantt/lib/Grid/feature/Filter.js';
import '../build-gantt/lib/Gantt/feature/Labels.js';
import '../build-gantt/lib/Gantt/feature/ProjectLines.js';
import '../build-gantt/lib/Gantt/feature/ProgressLine.js';
import '../build-gantt/lib/Gantt/feature/Rollups.js';
import '../build-gantt/lib/Gantt/feature/TaskEdit.js';
import '../build-gantt/lib/Gantt/feature/export/PdfExport.js';
import '../build-gantt/lib/Grid/feature/experimental/ExcelExporter.js';
import '../build-gantt/lib/Core/widget/ButtonGroup.js';
import '../build-gantt/lib/Scheduler/widget/UndoRedo.js';

const STATUS_DRAFT = 0;
const STATUS_PLANNING = 1;
const STATUS_ON_TRACK = 2;
const STATUS_DELAYED = 3;
const STATUS_COMPLETED = 4;
const STATUS_ON_HOLD = 5;
const STATUS_REJECTED = 6;
const STATUS_FAILED = 7;

const ROLE_SUPERVISOR = 1;
const ROLE_NORMAL = 0;

const projectId = 1;
const projectStartDate = '2021-01-01';
const projectEndDate = '2022-12-30';
const projectStatus = 1;
const userRole = 1;
const baselinesStr = "[]";
const userLocale = "En";

LocaleManager.applyLocale(userLocale);


// region Label configs
const
    topLabel    = {
        field  : 'name',
        editor : {
            type : 'textfield'
        }
    },
    bottomLabel = {
        field    : 'startDate',
        renderer : function({ taskRecord }) {
            return DateHelper.format(taskRecord.startDate, 'DD-MMM-Y');
        }
    };

// endregion

// using module bundle
LocaleManager.locale = userLocale;

// also possible to reach it from the gantt instance
//gantt.localeManager.locale = 'Fr';


const headerTpl = ({currentPage, totalPages}) => `
    <div class="demo-export-header">
        <img src="/assets/img/logo.png" />
        <dl>
            <dt>Date: ${DateHelper.format(new Date(), 'll LT')}</dt>
            <dd>${(currentPage && totalPages) ? `Page: ${currentPage + 1}/${totalPages}` : ''}</dd>
        </dl>
    </div>`;


const footerTpl = () => '<div class="demo-export-footer"><h3>© 2020 PMS</h3></div>';

class StatusColumn extends Column {
    static get $name() {
        return 'StatusColumn';
    }

static get type() {
    return 'statuscolumn';
}

static get isGanttColumn() {
    return true;
}

static get defaults() {
    return {
        // Set your default instance config properties here
        field      : 'status',
        text       : 'Status',
        editor     : false,
        cellCls    : 'b-status-column-cell',
        htmlEncode : false,
        filterable : {
            filterField : {
                type  : 'combo',
                items : ['Not Started', 'Started', 'Completed', 'Late']
            }
        }
    };
}

renderer({ isExport }) {

}

//endregion
renderer({ record ,isExport}) {
    const status = record.status;

    if (isExport) {
        return record.status;
    }
    else {
        // widget rendering routine
        return status ? {
            tag       : 'i',
            className : `b-fa b-fa-circle ${status}`,
            html      : status
        } : '';
    }


}
}

ColumnStore.registerColumnType(StatusColumn);

class MyTaskModel extends TaskModel {

static get fields() {
    return [
        {
            name : 'weight',
            type : 'number',
            convert(value, record) {
                // call standard number field convert() method
                value = this.constructor.prototype.convert.call(this, value, record);

                value = value ? Number(value) : 0;

                if(typeof(record) != "undefined") {
                    const parent = record.parent;

                    // sum up total weight for children
                    if (parent) {
                        let sum = value;

                        parent.children.forEach(task => {
                            if (task !== record) {
                                sum += task.weight;
                            }
                        });

                        // reduce if it's >= 100
                        if (sum > 100) {
                            value -= sum - 100;
                        }
                    }
                }
                return value;
            }
        }
    ]
}


* calculatePercentDone(proposedValue) {
    // if the event has sub-events
    if (yield * this.hasSubEvents()) {
        let parentWeight = 0;

        // get sub-events
        const subEvents = yield * this.subEventsIterable();

        for (const subEvent of subEvents) {
            // get the sub-event percentDone
            const percentDone = yield subEvent.$.percentDone;

            parentWeight += subEvent.weight * percentDone / 100;
        }

        return parentWeight;
    }

    // for leaf tasks use default logic
    return yield * super.calculatePercentDone(proposedValue);
}

get isLate() {
    return this.deadline && Date.now() > this.deadline;
}

get status() {
    let status = 'Not started';

    if (this.isCompleted) {
        status = 'Completed';
    }
    else if (this.endDate < Date.now() || (this.startDate<Date.now() && !this.isStarted)) {
        status = 'Late';
    }
    else if (this.isStarted) {
        status = 'Started';
    }

    return status;
}
}

function setBaseline(index) {
    gantt.taskStore.setBaseline(index);
}

function toggleBaselineVisible(index, visible) {
    //gantt.element.classList[visible ? 'remove' : 'add'](`b-hide-baseline-${index}`);
    console.log(`baseline-${index}`);
    var elements=document.getElementsByClassName(`baseline-${index}`);
    for (let i = 0; i < elements.length; i++) {
        elements[i].classList[visible ? 'remove' : 'add'](`hide-baseline`);
    }

}

let project = new ProjectModel({
    taskModelClass: MyTaskModel,
    autoSync: true,
    transport: {
        load: {
           // url: ""
            url: '../assets/build-gantt/examples/_datasets/launch-saas.json'
        },
        sync: {
            url: ""
        }
    },
    autoLoad: true,
    autoCalculatePercentDoneForParentTasks: false,

// The State TrackingManager which the UndoRedo widget in the toolbar uses
stm: {
    autoRecord: true
},

});


let baselines = JSON.parse(baselinesStr);
let baselinesList=[];
let index=1;

baselines.forEach(function(item){
    console.log("Baselines:");
    console.log(item);
    baselinesList.push(
        {
            checked: true,
            text: item.name,
            onToggle({checked}) {
                toggleBaselineVisible(item.id, checked);
            }
        }
    );
});


const readOnly = (projectStatus === STATUS_COMPLETED || projectStatus === STATUS_FAILED || userRole===ROLE_NORMAL) ? true : false;
const hideContextMenu=(projectStatus === STATUS_COMPLETED || projectStatus === STATUS_FAILED || userRole===ROLE_NORMAL || projectStatus === STATUS_ON_TRACK || projectStatus === STATUS_DELAYED) ? true : false;

function isFieldReadOnly(field) {
    var readOnly = false;
    switch (field) {
        case "percentdone":
            readOnly = (projectStatus === STATUS_PLANNING || projectStatus === STATUS_ON_HOLD || projectStatus === STATUS_REJECTED);
            break;
        case "":
            // code block
            break;
        default:
            readOnly = (projectStatus === STATUS_ON_TRACK || projectStatus === STATUS_DELAYED);
        // code block

}
return readOnly;

}

const apiRequestAction = async (method, url, headers, body) => {
    let fetchObject = {
        method,
        headers: { 'Content-Type': 'application/json', ...headers }
    };
    if (body) fetchObject = { ...fetchObject, body: JSON.stringify(body) };

const response = await fetch(url, { ...fetchObject });
return await response.json();
};

let gantt = new Gantt({
    project: project,
    readOnly: readOnly,
    startDate: new Date(projectStartDate),
    endDate: new Date(projectEndDate),

columns: [
    {type: 'sequence',text: '#' },
    {type: 'wbs',
        renderer({ value }) {
            return String(value);
        }
    },
    {type: 'name', field: 'name', text: Localizable().L('L{NameColumn.Name}'), editor: !isFieldReadOnly("name"), filterable: true },
    {
        type: 'number',
        min: 0,
        max: 100,
        text:Localizable().L('Weight'),
        align: 'center',
        field: 'weight',
        editor: !isFieldReadOnly("weight"),
        filterable: true
    },
    {type: 'startdate', text: Localizable().L('L{StartDateColumn.Start}'), editor: !isFieldReadOnly("startdate"), filterable: true,min: projectStartDate,max: projectEndDate},
    { type:'enddate', text: Localizable().L('L{EndDateColumn.Finish}'), editor: !isFieldReadOnly("enddate"), filterable: true,min: projectStartDate,max: projectEndDate },
    {type: 'duration', text: Localizable().L('L{DurationColumn.Duration}'), editor: !isFieldReadOnly("duration"), filterable: true },
    { type : 'statuscolumn', filterable: true},

    {type: 'resourceassignment', width: 120, showAvatars: true, editor: !isFieldReadOnly("resourceassignment"), filterable: true },
    {
        type: 'percentdone',
        text: Localizable().L('L{PercentDoneColumn.% Done}'),
        showCircle: true,
        width: 70,
        editor: !isFieldReadOnly("percentdone"),
        filterable: true,
        renderer({ record, isExport }) {
            const value = record.renderedPercentDone;

            if (this.showCircle && !isExport) {
                return {
                    className : {
                        'b-percentdone-circle' : 1,
                        'b-full'               : value === 100,
                        'b-empty'              : value === 0
                    },
                    style     : {
                        height                      : this.circleHeightPercentage * this.grid.rowHeight + 'px',
                        width                       : this.circleHeightPercentage * this.grid.rowHeight + 'px',
                        '--gantt-percentdone-angle' : `${value / 100}turn`
                    },
                    dataset : {
                        value
                    }
                };

            }

            return value;
        }
    },
    {type: 'predecessor', editor: !isFieldReadOnly("predecessor"), filterable: true },
    {type: 'successor', editor: !isFieldReadOnly("successor"), filterable: true },
    //{ type : 'milestone' },

    //{ type : 'schedulingmodecolumn' },
    {type : 'calendar'},
    //{type: 'constrainttype', editor: !isFieldReadOnly("constrainttype")},
    //{type: 'constraintdate', editor: !isFieldReadOnly("constraintdate")},
    //{type: 'date', text: 'Deadline', field: 'deadline', editor: !isFieldReadOnly("deadline"), filterable: true },

    //{type: 'date', text: 'Finish', field: 'enddate', editor: !isFieldReadOnly("finish")},

    { type: 'addnew', field: 'addnew', text: 'Addnew' },

],

style: 'font-size:0.85em',
autoHeight: true,
subGridConfigs: {
    locked: {flex: 3},
    normal: {flex: 4}
},
listeners: {
    beforeCellEditStart({editorContext}) {
        return Boolean(editorContext.record.parentId);
    },
    beforeTaskEdit({ taskRecord, taskElement }) {

        if(!taskRecord.parentId)
        {
            // Prevent built in editor
            return false;
        }


    },
    beforeTaskEditShow({ editor, taskRecord }) {


        console.log(editor.widgetMap);
        editor.widgetMap.constraintDateField.hidden=true;
        editor.widgetMap.constraintTypeField.hidden=true;
        editor.widgetMap.manuallyScheduledField.hidden=true;
        editor.widgetMap.rollupField.hidden=true;
        editor.widgetMap.divider.visible=false;


    },
    paint : ({ source }) => {
        // find "Add New..." column
        // const addNewColumn = source.columns.find(col => col.isAddNewColumn);
        //console.log(addNewColumn.columnCombo.store);

        // filter the column drop down list
        //addNewColumn.columnCombo.store.addFilter
        source.columns.findRecord('type', 'addnew').combo.store.addFilter({
            id       : 'my-filter',
            // define filter function that will skip columns of certain types
            filterBy : (column) => {
                return column.id == 'earlystartdate' || column.id == 'earlyenddate' || column.id == 'lateenddate' || column.id == 'latestartdate' || column.id == 'effort' || column.id == 'totalslack' || column.id == 'milestone' || column.id == 'note' || column.id == 'schedulingmode';
            }
        });
    }
},
features: {
    criticalPaths: true,
    cellEdit: {
        addNewAtEnd: false,
    },

    rowReorder : {
        listeners : {
            gridRowBeforeDropFinalize({ context }) {
                return Boolean(context.insertBefore.parentId);
            }

        }
    },

    filter: true,
    taskEdit: {
        disabled : hideContextMenu,
        items: {
            generalTab: {
                items: {
                    name: { readOnly: isFieldReadOnly("name") },
                    startDate: { readOnly: isFieldReadOnly("startdate") },
                    endDate: { readOnly: isFieldReadOnly("enddate") },
                    duration: { readOnly: isFieldReadOnly("duration") },
                    statusColumn: { readOnly: isFieldReadOnly('statuscolumn') },
                    percentDone: { readOnly: isFieldReadOnly("percentDone") },
                    effort: { readOnly: isFieldReadOnly("effort") },
                    deadLine: { readOnly: isFieldReadOnly("deadline") },
                    newGeneralField: {
                        type: 'number',
                        weight: 710,
                        label: 'Weight',
                        name: 'weight',
                        min: 0,
                        max: 100,
                        readOnly: isFieldReadOnly('weight')
                    }
                }
            },
            resourcesTab: {
                items: {
                    resourceassignment: { readOnly: isFieldReadOnly("resourceassignment") },
                }
            },
            advancedTab: {
                items: {
                    constraintType: false,
                    constraintDate: false,
                    divider     : false
                }
            }
        }
    },

    taskMenu: {
        processItems({items, taskRecord}) {
            const readOnly = !taskRecord.parentId;
            console.log(items);
            items.add.menu.addTaskAbove.hidden =
                items.add.menu.addTaskBelow.hidden =
                    items.editTask.hidden =
                        items.deleteTask.hidden =
                            readOnly;
            //items.add.menu.subtask.hidden = !readOnly;
            items.add.menu.successor.hidden=readOnly;
            items.add.menu.predecessor.hidden=readOnly;
            items.add.menu.milestone.hidden=readOnly;

            items.convertToMilestone.hidden=readOnly;

            //items.add.menu.filter.hidden=readOnly;
            items.indent.hidden=readOnly;
            items.outdent.hidden=readOnly;
            if(typeof(items.filterStringEquals) != "undefined") {
                items.filterStringEquals.hidden=readOnly;
            }
            console.log("Level : "+taskRecord.childLevel);
            if(taskRecord.childLevel==1){
                items.outdent.hidden=true;
            }

            console.log(hideContextMenu);
            if(hideContextMenu){
                return false;
            }

            if(!readOnly){

                items.add.menu.addTaskAbove.onItem=async ({taskRecord}) => {
                    const task = await gantt.addTaskAbove(taskRecord);
                    task.name = 'New task';
                    task.duration = 2;
                    task.calendar=gantt.project.calendar.id;
                    gantt.project.commitAsync();
                }

                items.add.menu.addTaskBelow.onItem=async ({taskRecord}) => {
                    const task = await gantt.addTaskBelow(taskRecord);
                    task.name = 'New task';
                    task.duration = 2;
                    task.calendar=gantt.project.calendar.id;
                    gantt.project.commitAsync();
                }

            }

            items.add.menu.subtask.onItem=async ({taskRecord}) => {
                const task = await gantt.addSubtask(taskRecord);
                task.name = 'New subtask';
                task.duration = 2;
                console.log(gantt.project.calendar.id);
                task.calendar=gantt.project.calendar.id;
                gantt.project.commitAsync();

                console.log(task.duration);
            }

        },

    },

    // baselines : true,
    baselines: {
        // Custom tooltip template for baselines
        template(data) {
            const
                me = this,
                {baseline} = data,
                {task} = baseline,
                delayed = task.startDate > baseline.startDate,
                overrun = task.durationMS > baseline.durationMS;

            console.log(baseline);

            let {decimalPrecision} = me;

            if (decimalPrecision == null) {
                decimalPrecision = me.client.durationDisplayPrecision;
            }

            const
                multiplier = Math.pow(10, decimalPrecision),
                displayDuration = Math.round(baseline.duration * multiplier) / multiplier;

            return `
                <div class="b-gantt-task-title">${StringHelper.encodeHtml(task.name)} (${baseline.name})</div>
                <table>
                <tr><td>${me.L('Start')}:</td><td>${data.startClockHtml}</td></tr>
                ${baseline.milestone ? '' : `
                    <tr><td>${me.L('End')}:</td><td>${data.endClockHtml}</td></tr>
                    <tr><td>${me.L('Duration')}:</td><td class="b-right">${displayDuration + ' ' + DateHelper.getLocalizedNameOfUnit(baseline.durationUnit, baseline.duration !== 1)}</td></tr>
                `}
                </table>
                ${delayed ? `
                    <h4 class="statusmessage b-baseline-delay"><i class="statusicon b-fa b-fa-exclamation-triangle"></i>${me.L('Delayed start by')} ${DateHelper.formatDelta(task.startDate - baseline.startDate)}</h4>
                ` : ''}
                ${overrun ? `
                    <h4 class="statusmessage b-baseline-overrun"><i class="statusicon b-fa b-fa-exclamation-triangle"></i>${me.L('Overrun by')} ${DateHelper.formatDelta(task.durationMS - baseline.durationMS)}</h4>
                ` : ''}
                `;
        }
    },
    pdfExport: {
        exportServer: 'http://localhost:8080', //Print server
        translateURLsToAbsolute : 'http://localhost:8080/resources', //Print server
        clientURL : 'http://localhost:8080', //App server
        headerTpl,
        footerTpl
    },
    excelExporter: {
        // Choose the date format for date fields
        dateFormat: 'YYYY-MM-DD HH:mm'
    },
    /*labels : {
        top: topLabel,
        bottom: null,
        left: null,
        right: null,
       labelCls: 'label-css'
    }*/
},
dependencyIdField: 'wbsCode',
tbar: {
    items: [
        {
            type: 'buttonGroup',
            items: [
                /*{
                    type: 'button',
                    text: 'Add Task',

                    onAction() {
                        // console.log(project.taskStore.first.appendChild({
                            name: 'New Task', duration: '1'
                        }));
                    }
                },*/
                {
                    color: 'b-green',
                    ref: 'addTaskButton',
                    icon: 'b-fa b-fa-plus',
                    text: 'Create',
                    tooltip: 'Create new task',
                    style: 'margin-right: .5em',
                    disabled: readOnly || isFieldReadOnly("name"),
                    async onAction() { //onAddTaskClick

                        const newTask = {name: 'New task', duration: '2', calendar: gantt.project.calendar.id};
                        console.log("From create task:");
                        console.log(gantt.project.calendar.id);

                        //const added = gantt.taskStore.rootNode.appendChild(newTask);
                        let added =project.taskStore.first.appendChild({
                            name: 'New Task', duration: '2',weight:'0',calendar: gantt.project.calendar.id
                        });

                        // run propagation to calculate new task fields
                        // await project.comitAsync();

                        // run propagation to calculate new task fields
                        await gantt.project.propagateAsync();


                        // scroll to the added task. Smoothly.
                        await gantt.scrollRowIntoView(added, {
                            animate: true
                        });

                        gantt.features.cellEdit.startEditing({
                            record: added,
                            field: 'name'
                        });
                        Toast.show('Created a new task, you may update it at the same or click somewhere else to avoid update');
                        /*Toast.show({
                            html: 'Created a new task, you may update it at the same or click somewhere else to avoid update',
                            color: 'b-light-green'
                        });*/
                    }
                }
            ]
        },
        {
            type: 'buttonGroup',
            items: [
                {
                    color: 'b-green',
                    icon: 'b-fa b-fa-pen',
                    text: 'Edit',
                    tooltip: 'Edit selected task',
                    disabled: readOnly || isFieldReadOnly("name"),
                    onAction() { //onEditTaskClick
                        if (gantt.selectedRecord && gantt.selectedRecord.parentId) {
                            gantt.editTask(gantt.selectedRecord);
                        }else if (gantt.selectedRecord && !gantt.selectedRecord.parentId) {
                            Toast.show('Sorry, you can not edit project task');
                        }else {
                            Toast.show('First select the task you want to edit');
                        }
                    }
                }
            ]
        },
        {
            ref: 'undoRedo',
            type: 'undoredo',
            items: {
                transactionsCombo: null
            }
        },
        {
            type: 'buttonGroup',
            items: [
                {
                    ref: 'expandAllButton',
                    icon: 'b-fa b-fa-angle-double-down',
                    tooltip: 'Expand all',
                    onAction() { //onExpandAllClick
                        gantt.expandAll();
                    }
                },
                {
                    ref: 'collapseAllButton',
                    icon: 'b-fa b-fa-angle-double-up',
                    tooltip: 'Collapse all',
                    onAction() { //onCollapseAllClick
                        gantt.collapseAll();
                    }
                }
            ]
        },
        {
            type: 'buttonGroup',
            items: [
                {
                    ref: 'zoomInButton',
                    icon: 'b-fa b-fa-search-plus',
                    tooltip: 'Zoom in',
                    onAction() { //onZoomInClick
                        gantt.zoomIn();
                    }
                },
                {
                    ref: 'zoomOutButton',
                    icon: 'b-fa b-fa-search-minus',
                    tooltip: 'Zoom out',
                    onAction() { //onZoomOutClick
                        gantt.zoomOut();
                    }
                },
                {
                    ref: 'zoomToFitButton',
                    icon: 'b-fa b-fa-compress-arrows-alt',
                    tooltip: 'Zoom to fit',
                    onAction() {
                        gantt.zoomToFit({ //onZoomToFitClick
                            leftMargin: 50,
                            rightMargin: 50
                        });
                    }
                },
                {
                    ref: 'previousButton',
                    icon: 'b-fa b-fa-angle-left',
                    tooltip: 'Previous time span',
                    onAction() { //'up.onShiftPreviousClick'
                        gantt.shiftPrevious();
                    }
                },
                {
                    ref: 'nextButton',
                    icon: 'b-fa b-fa-angle-right',
                    tooltip: 'Next time span',
                    onAction() { //'up.onShiftNextClick'
                        gantt.shiftNext();
                    }
                }
            ]
        },



        {
            type: 'buttonGroup',
            items: [
                {
                    type: 'button',
                    text: 'Export to Excel',
                    ref: 'excelExportBtn',
                    icon: 'b-fa-file-export',
                    onAction: () => {
                        const filename = gantt.project.taskStore.first && gantt.project.taskStore.first.name;
                        gantt.features.excelExporter.export({
                            filename
                        });
                    }
                },

                {
                    type : 'button',
                    ref  : 'exportButton',
                    icon : 'b-fa-file-export',
                    text : 'Export to PDF',
                    onClick() {
                        gantt.zoomToFit({ //onZoomToFitClick
                            leftMargin: 50,
                            rightMargin: 50
                        });
                        gantt.features.pdfExport.showExportDialog();
                    }
                }
            ]
        },
        '->',
        {
            type      : 'combo',
            ref       : 'calendar',
            label     : 'Default calendar',
            items : [{ value: 'general', text: 'General' }, { value: 'business', text: 'Business' }],
            flex      : '0 0 17em',
            text : 'Default calendar description',
            readOnly:true,
            onChange:({ value, oldValue }) => {
                if (!oldValue) {
                    return;
                }

                console.log(value);


                apiRequestAction('POST', '', {}, {"projectId":projectId,"calendar":value})
                    .then(res => {
                        //loadSpinner(false);
                        console.log('res => ', res);
                    })
                    .catch(err => {
                        //loadSpinner(false);
                        console.log('err => ', err);
                    });

                if(gantt.project.taskStore.first){
                }
                console.log("From change calendar:");
                console.log(gantt.project.getCalendar());
            }
        },

        '->',

        {
            type: 'textfield',
            ref: 'filterByName',
            cls: 'filter-by-name',
            flex: '0 0 12.5em',
            // Label used for material, hidden in other themes
            label: 'Find tasks by name',
            // Placeholder for others
            placeholder          : 'Find tasks by name',
            clearable: true,
            keyStrokeChangeDelay: 100,
            triggers: {
                filter: {
                    align: 'end',
                    cls: 'b-fa b-fa-filter'
                }
            },
            onChange({value}) { //'up.onFilterChange'
                if (value === '') {
                    gantt.taskStore.clearFilters();
                } else {
                    value = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

                    gantt.taskStore.filter({
                        filters: task => task.name && task.name.match(new RegExp(value, 'i')),
                        replace: true
                    });
                }
            }
        },

        {
            type: 'buttonGroup',
            items: [
                {
                    type: 'button',
                    text: 'Show baseline',
                    icon: 'b-fa-bars',
                    iconAlign: 'end',
                    menu: baselinesList
                }
            ]
        },
        {
            type: 'checkbox',
            text: 'Show baselines',
            checked: true,
            ref:'showBaselines',
            toggleable: true,
            onAction({checked}) {
                gantt.features.baselines.disabled = !checked;
            }
        },

        {
            type: 'button',
            ref: 'featuresButton',
            icon: 'b-fa b-fa-tasks',
            text: 'Settings',
            tooltip: 'Toggle features',
            toggleable: true,
            menu: {
                onItem({source: item}) { //'up.onFeaturesClick',
                    if (item.feature) {
                        const feature = gantt.features[item.feature];
                        feature.disabled = !feature.disabled;
                    } else if (item.subGrid) {
                        const subGrid = gantt.subGrids[item.subGrid];
                        subGrid.collapsed = !subGrid.collapsed;
                    }
                },
                onBeforeShow({source: menu}) { //'up.onFeaturesShow',
                    menu.items.map(item => {
                        const {feature} = item;

                        if (feature) {
                            // a feature might be not presented in the gantt
                            // (the code is shared between "advanced" and "php" demos which use a bit different set of features)
                            if (gantt.features[feature]) {
                                item.checked = !gantt.features[feature].disabled;
                            }
                            // hide not existing features
                            else {
                                item.hide();
                            }
                        } else if (item.subGrid) {
                            item.checked = gantt.subGrids[item.subGrid].collapsed;
                        }
                    });
                },

                // "checked" is set to a boolean value to display a checkbox for menu items. No matter if it is true or false.
                // The real value is set dynamically depending on the "disabled" config of the feature it is bound to.
                items: [
                    {
                        text: 'UI settings',
                        icon: 'b-fa-sliders-h',
                        menu: {
                            type: 'popup',
                            anchor: true,
                            cls: 'settings-menu',
                            layoutStyle: {
                                flexDirection: 'column'
                            },
                            onBeforeShow({source: menu}) { //'up.onSettingsShow',
                                const {rowHeight, barMargin, duration} = menu.widgetMap;

                                rowHeight.value = gantt.rowHeight;
                                barMargin.value = gantt.barMargin;
                                barMargin.max = (gantt.rowHeight / 2) - 5;
                                duration.value = gantt.transitionDuration;
                            },

                            items: [
                                {
                                    type: 'slider',
                                    ref: 'rowHeight',
                                    text: 'Row height',
                                    width: '12em',
                                    showValue: true,
                                    min: 30,
                                    max: 70,
                                    onInput({value, source}) { //'up.onRowHeightChange'
                                        gantt.rowHeight = value;
                                        source.owner.widgetMap.barMargin.max = (value / 2) - 5;
                                    }
                                },
                                {
                                    type: 'slider',
                                    ref: 'barMargin',
                                    text: 'Bar margin',
                                    width: '12em',
                                    showValue: true,
                                    min: 0,
                                    max: 10,
                                    onInput({value}) { //'up.onBarMarginChange'
                                        gantt.barMargin = value;
                                    }
                                },
                                {
                                    type: 'slider',
                                    ref: 'duration',
                                    text: 'Animation duration ',
                                    width: '12em',
                                    min: 0,
                                    max: 2000,
                                    step: 100,
                                    showValue: true,
                                    onInput({value}) { //'up.onAnimationDurationChange'
                                        gantt.transitionDuration = value;
                                        styleNode.innerHTML = `.b-animating .b-gantt-task-wrap { transition-duration: ${value / 1000}s !important; }`;
                                    }
                                }
                            ]
                        }
                    },
                    {
                        text: 'Draw dependencies',
                        feature: 'dependencies',
                        checked: false
                    },
                    {
                        text: 'Task labels',
                        feature: 'labels',
                        checked: false
                    },
                    {
                        text: 'Critical paths',
                        feature: 'criticalPaths',
                        tooltip: 'Highlight critical paths',
                        checked: false
                    },
                    {
                        text: 'Project lines',
                        feature: 'projectLines',
                        checked: false
                    },
                    {
                        text: 'Highlight non-working time',
                        feature: 'nonWorkingTime',
                        checked: false
                    },
                    /* {
                        text: 'Enable cell editing',
                        feature: 'cellEdit',
                        checked: false
                    }, */
                    /* {
                        text: 'Show baselines',
                        feature: 'baselines',
                        checked: false
                    }, */
                    {
                        text: 'Show rollups',
                        feature: 'rollups',
                        checked: false
                    },
                    {
                        text: 'Show progress line',
                        feature: 'progressLine',
                        checked: false
                    },
                    {
                        text: 'Hide schedule',
                        cls: 'b-separator',
                        subGrid: 'normal',
                        checked: false
                    }
                ]
            }
        }
    ]
},
appendTo: document.getElementById('granttId')
});




/*gantt.taskStore.on({
    catchAll(event) {
        if(event.type ==='add'){
            console.log('Event Listener catch All (ADD TASK)....', event.type, event)

        console.log(event.records[0]);
    }
}
})*/

gantt.taskStore.on({
    add:(event) => {
        console.log('TaskStore Event ADD', event.records[0]);

    let record=event.records[0];
    let value = record.weight;

    value = value ? Number(value) : 0;

    if(typeof(record) != "undefined") {
        const parent = record.parent;

        // sum up total weight for children
        if (parent) {
            let sum = value;

            parent.children.forEach(task => {
                if (task !== record) {
                    sum += task.weight;
                }
            });

            // reduce if it's >= 100
            if (sum > 100) {
                value -= sum - 100;
            }
        }
    }

    record.weight=value;

},
});

gantt.project.on({
    load:(event) => {
        updateProjectCalendar();
    }
});

function updateProjectCalendar(){
    console.log(gantt.project.calendar);
    gantt.tbar.widgetMap.calendar.value=gantt.project.calendar.id;
}
gantt.tbar.widgetMap.showBaselines.checked=false;

Post by marcio »

Hey jeanbaptiste.minani,

There is a warning regarding a listener that will be deprecated

VersionHelper.js:227 Deprecation warning: You are using a deprecated API which will change in v6.0.0. gridRowBeforeDropFinalize event is deprecated, listen on this event on the Grid instead.:

Besides that, when I load the project, it shows an empty screen, even though it displays the console.logs (I added a custom one to show the Gantt).

Could you get one of our demos and add your code to that and make it runnable?

Attachments
Screenshot 2023-05-26 at 15.47.06.png
Screenshot 2023-05-26 at 15.47.06.png (496.28 KiB) Viewed 753 times

Best regards,
Márcio


Post Reply