Hi,
In Bryntum Gantt TaskEdit popup, we have a custom tab in which we are using the Combo dropdown widget. We are able to load the data and achieve the desired functionality except for the following issues, for which we could use some help.
' text field ' is not cleared/reset when the task editor is closed : When the task editor is closed, the 'text field' in the Combo (dropdown) widget does not reset, so when we try to reopen the task editor the previously selected multiple options still show up in the text field. (please see updated code for Bryntum 'Advanced' example.
We have added a 'Delete' button in the Grid (seen in the Custom Tab config code below) and 'Add' button in the toolbar and we'd like to refresh the grid data instantly on button click without having to close the editor. Please advise on how this can be achieved.
Updated code for Bryntum Gantt Advanced example (with combo widget)
import { Combo, StringHelper, Grid, List, Gantt, ProjectModel, TaskModel } from '../../build/gantt.module.js?467719';
import shared from '../_shared/shared.module.js?467719';
const baseColors = [
'maroon', 'red', 'orange', 'yellow',
'olive', 'green', 'purple', 'fuchsia',
'lime', 'teal', 'aqua', 'blue', 'navy',
'black', 'gray', 'silver', 'white'
];
class ColorField extends Combo {
// Factoryable type name
static get type() {
return 'colorfield';
}
static get defaultConfig() {
return {
clearable : true,
items : baseColors,
picker : {
cls : 'b-color-picker-container',
itemCls : 'b-color-picker-item',
itemTpl : item => `<div style="background-color:${item.id}"></div>`
}
};
}
}
// Register this widget type with its Factory
ColorField.initClass();
/**
* @module FilesTab
*/
/**
* @internal
*/
class FilesTab extends Grid {
// Factoryable type name
static get type() {
return 'filestab';
}
static get defaultConfig() {
return {
title : 'Files',
defaults : {
labelWidth : 200
},
columns : [{
text : 'Files attached to task',
field : 'name',
type : 'template',
template : data => StringHelper.xss`<i class="b-fa b-fa-fw b-fa-${data.record.data.icon}"></i>${data.record.data.name}`
}]
};
}
loadEvent(eventRecord) {
const files = [];
for (let i = 0; i < Math.random() * 10; i++) {
const nbr = Math.round(Math.random() * 5);
switch (nbr) {
case 1:
files.push({
name : `Image${nbr}.pdf`,
icon : 'image'
});
break;
case 2:
files.push({
name : `Charts${nbr}.pdf`,
icon : 'chart-pie'
});
break;
case 3:
files.push({
name : `Spreadsheet${nbr}.pdf`,
icon : 'file-excel'
});
break;
case 4:
files.push({
name : `Document${nbr}.pdf`,
icon : 'file-word'
});
break;
case 5:
files.push({
name : `Report${nbr}.pdf`,
icon : 'chart-line'
});
break;
}
}
this.store.data = files;
}
}
// Register this widget type with its Factory
FilesTab.initClass();
/**
* @module ResourceList
*/
/**
* @internal
*/
class ResourceList extends List {
// Factoryable type name
static get type() {
return 'resourcelist';
}
static get configurable() {
return {
title : 'Resources',
cls : 'b-inline-list',
items : [],
itemTpl : resource => {
return StringHelper.xss`
<img src="../_shared/images/users/${resource.name.toLowerCase()}.jpg">
<div class="b-resource-detail">
<div class="b-resource-name">${resource.name}</div>
<div class="b-resource-city">
${resource.city}
<i data-btip="Deassign resource" class="b-icon b-icon-trash"></i>
</div>
</div>
`;
}
};
}
// Called by the owning TaskEditor whenever a task is loaded
loadEvent(taskRecord) {
this.taskRecord = taskRecord;
this.store.data = taskRecord.resources;
}
// Called on item click
onItem({ event, record }) {
if (event.target.matches('.b-icon-trash')) {
// Unassign the clicked resource record from the currehtly loaded task
this.taskRecord.unassign(record);
// Update our store with the new assignment set
this.store.data = this.taskRecord.resources;
}
}
}
// Register this widget type with its Factory
ResourceList.initClass();
class MyModel extends TaskModel {
static get fields() {
return [
{ name : 'deadline', type : 'date' },
{ name : 'color' }
];
}
}
const project = window.project = new ProjectModel({
taskModelClass : MyModel,
transport : {
load : {
url : '../_datasets/launch-saas.json'
}
},
// 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({
appendTo : 'container',
features : {
taskEdit : {
items : {
generalTab : {
// change title of General tab
title : 'Common',
items : {
customDivider : {
html : '',
dataset : {
text : 'Custom fields'
},
cls : 'b-divider',
flex : '1 0 100%'
},
deadlineField : {
type : 'datefield',
name : 'deadline',
label : 'Deadline',
flex : '1 0 50%',
cls : 'b-inline'
},
colorField : {
type : 'colorfield',
name : 'color',
label : 'Color',
flex : '1 0 50%',
cls : 'b-inline'
},
priority : {
type : 'radiogroup',
name : 'priority',
label : 'Priority',
flex : '1 0 100%',
options : {
high : 'High',
med : 'Medium',
low : 'Low'
}
},
myCombo: {
type: 'combo',
label: 'Select an HRA',
items: ['A','B','C','D'],
editable: true,
multiSelect: true,
style: {
padding: '5px',
},
clearTextOnPickerHide: true
},
}
},
// remove Notes tab
notesTab : false,
// add custom Files tab to the second position
filesTab : {
type : 'filestab',
weight : 110
},
// add custom Resources tab to the third position
resourcesTab : {
type : 'resourcelist',
weight : 120,
title : 'Resources'
},
// customize the predecessors grid
predecessorsTab : {
items : {
grid : {
columns : {
data : {
// Our definition of the name column
// is merged into the existing one.
// We just add some configurations.
name : {
// Grid cell values are rendered with extra info.
renderer({ record : dependency }) {
const predecessorTask = dependency.fromTask;
if (predecessorTask) {
return StringHelper.xss`${predecessorTask.name} (${predecessorTask.id})`;
}
return '';
},
// The cell editor, and its dropdown list
// also have this extra info.
editor : {
displayValueRenderer(taskRecord) {
return taskRecord ? StringHelper.xss`${taskRecord.name} (${taskRecord.id})` : '';
},
listItemTpl(taskRecord) {
return StringHelper.xss`${taskRecord.name} (${taskRecord.id})`;
}
}
}
}
}
}
}
}
}
}
},
taskRenderer : ({ taskRecord, renderData }) => {
if (taskRecord.color) {
renderData.style += `background-color:${taskRecord.color}`;
}
},
columns : [
{ type : 'name', field : 'name', text : 'Name', width : 250 },
{ type : 'date', field : 'deadline', text : 'Deadline' }
],
project,
dependencyIdField : 'sequenceNumber'
});
project.load();
Custom Tab config for reference
getHRATabTaskEditConfig = (
HRAcomboItems = ['HRA1', 'HRA2', 'HRA3'],
emitHraInputForAssign: (arg0: any) => void,
addHRAsTrigger: () => void,
removeHRATrigger: () => void,
emitHRAInputForRemove: (arg0: any) => void,
) => {
const onHraInput = (event: any) => {
emitHraInputForAssign(event.source._valueCollection?._values);
};
const addHRAs = ({ source : { parent : { parent : { parent : taskEditor } } } }: { source: { parent: { parent: { parent: any } } } }) => {
addHRAsTrigger();
// taskEditor.close() // I get an error message saying taskEditor.close() is not defined. Maybe the button from toolbar triggers a different event?
}
const removeHRAs = (event: Event | undefined, btn: any) => {
emitHRAInputForRemove(btn);
removeHRATrigger()
}
return {
HRATab: {
scrollIntoView: true,
title: 'HRA',
weight: 500,
items: {
grid: {
width: '100%',
height: '60%',
style: { overflowX: 'scroll' },
type: 'grid',
columns: {
data: {
hraid: {
field: 'hra_id',
text: 'HRA Id',
width: '45%',
editor: {
readOnly: true,
},
style: {
borderLeft: '1px solid #000',
},
},
hradescription: {
field: 'hra_description',
text: 'HRA Description',
width: '45%',
editor: {
readOnly: true,
},
},
delete: {
type: 'widget',
field: 'widget',
width: '10%',
widgets: [
{
type: 'button',
icon: `<i class="b-fa b-fa-trash b-black"></i>`,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
onClick: ({ event, source: btn }) => {
removeHRAs(event, btn)
}
},
],
},
},
},
},
myCombo: {
type: 'combo',
label: 'Select an HRA',
listeners: {
change: onHraInput,
},
value:'',
items: HRAcomboItems,
editable: true,
multiSelect: true,
style: {
padding: '5px',
},
clearTextOnPickerHide: true
},
toolbar: {
type: 'toolbar',
width: '50%',
items: {
addButton: { text: 'Add',
icon: 'b-fa b-fa-plus',
onClick: addHRAs,
},
cancelButton: {
text: 'Cancel',
icon: 'b-fa b-fa-times' ,
// onClick: resetMultiSelect
},
},
},
},
},
};
};