Premium support for our pure JavaScript UI components


Post by chrisb »

Hey Guys,

Not sure if this is a bug, or I'm doing something wrong.

I have a custom Tab in the TaskEditor in Gantt - with a combo field, whose items is populated by a store. I've got everything working ok, except the data in the property on my task does not show in the combo. I have other combos on the same tab populated with static items that work fine.

My task model has the field specified:

static get fields() {
    return [
      'statusColor',
      'description',
      'teamUuid',
      'team',
      'roleUuids',
      'roles',
      'deal',
      'locked',
      'ownerUsernames',
      'owners',
      'phase'
    ];
  }

The combo is defined as such:

    ownerPickerField: {
          type: 'combo',
          label: 'Owners',
          multiSelect: true,
          name: 'ownerUsernames',
          emptyText: 'Search for a person',
          valueField: 'username',
          displayField: 'fullName',
          minChars: 3,
          keyStrokeFilterDelay: 300,
          hideTrigger: false,
          hidePickerOnSelect: true,
          listItemTpl: ({ data }) => data.fullName,
          chipView: {
            itemTpl: ({ data }) => data.fullName
          },
          filterParamName: 'query',
          encodeFilterParams: (filters) => {
            return filters[0].value;
          },
          store: ResourcesTab.peopleStore
        }

The store is defined by the following; The initial load here is some custom functionality we need to have an initial list of certain users to pick from, then the remote filtering will kick in when a search field is provided.

ResourcesTab.peopleStore = new AjaxStore({
      readUrl: 'api/v3/people',
      onBeforeRequest: ({ params }) => {
        if (!Object.keys(params)?.length) {
          params.usernames = [
            ...(ResourcesTab.roleUsernames || []),
            ...(ResourcesTab.currentTask?.ownerUsernames || [])
          ];
        }
      }
    });

I added a change listener to the combo, so I know the data is there, as the value on the change listener is populated on load. I assumed it was because the usernames in ownerUsernames was not loaded in the store initially, so I added them to the initial load. However it is still not showing up.

Should the AjaxStore do a request to load data about the current value on load? What am I missing?

Thanks,

Chris


Post by alex.l »

Hi Chris,

I need a full test case to help you. Could you please apply minimal changes to one of our examples to reproduce the problem, zip and attach it here?
I don't see how do you set values, record value format, store data format, how and when do you load your AjaxStore, since no https://bryntum.com/docs/gantt/#Core/data/AjaxStore#config-autoLoad has been specified.
Did you try to set value after the TaskEditor has been shown, just for debugging purposes.
Try to set it here https://bryntum.com/docs/gantt/#SchedulerPro/feature/TaskEdit#event-beforeTaskEditShow
https://bryntum.com/docs/gantt/#guides/customization/taskedit.md#update-custom-fields-state-and-data

All the best,
Alex

All the best,
Alex


Post by chrisb »

Hey Alex,

I'll try and find some time to create a repro, just going to be a biggish job, as it's going to need a backend of some description.

In the mean time, I tried autoLoad, that just seems to call the readUrl with no filter or params, which for our use case isn't suitable due to the number of records that will be returned.

I'm hoping if I provide enough information we can figure out how it's supposed to work, and how I can modify what I'm doing.

The task has an array of usernames in the ownerUsernames property, I have this bound to a combo on a custom tab. That combo is backed by an ajax store. The combo uses the chipView.itemTpl to show the fullName property of the user. The ownerUsernames field has a value as shown below;
Image

I've been trying some things in onBeforeTaskEditShow. I've tried setting the items of the combo, tried setting the data of the AjaxStore, tried autoLoad, tried manually setting the value of the combo.

Even manually setting the combo value doesn't seem to work properly, as setting it to the array of values, seems to internally set it to the first item in the array, but the accessor for value shows an empty array.
ImageImage

The combo also seems to never get set to the value specified by the value of the field set to "name" - in this case - ownerUsernames.

resourcesTab.widgetMap['ownerPickerField'].value = task.ownerUsernames;
    resourcesTab.widgetMap['ownerPickerField'].items = task.owners;
    resourcesTab.widgetMap['ownerPickerField'].store = new AjaxStore({
      readUrl: 'api/v3/people',
      data: task.task.owners,
      autoLoad: true,
      onBeforeRequest: ({ params }) => {
        debugger;
        if (!Object.keys(params)?.length) {
          params.usernames = ResourcesTab.roleUsernames || [];
        }
      }
    });
    

Post by alex.l »

I've found some inconsistency in work with AjaxStore in our combo that I am currently discussing with the team and will notify you about later. As a workaround, you could try to set value to the combo after the combo has been loaded:


listeners : {
    beforeTaskEditShow({ taskRecord, editor }) {
        const { ownerNamesCombo } = editor.widgetMap;
        ownerNamesCombo.store.on('load', () => customCombo.value = taskRecord.ownerUsernames);
        ownerNamesCombo.store.params = { extra : "params" };
        ownerNamesCombo.store.load();
},

data format for ownerUsernames field of your record is:

[ "ownerName1", "ownerName2" ]

data format for the records in your combo store:

[{
    username : 'ownerName1',
    fullName : 'Full User Name 1'
}, {
     username : 'ownerName2',
     fullName : 'Full User Name 2'
}]

I've tested it in our taskeditor demo for Gantt.

Two notes about the code you posted.

    resourcesTab.widgetMap['ownerPickerField'].items = task.owners; // #1
    resourcesTab.widgetMap['ownerPickerField'].store = new AjaxStore({
      readUrl: 'api/v3/people',
      data: task.task.owners, // #2
      autoLoad: true,
      onBeforeRequest: ({ params }) => {
        debugger;
        if (!Object.keys(params)?.length) {
          params.usernames = ResourcesTab.roleUsernames || [];
        }
      }
    });

#1 - you should use or items or store, not together. So please remove items.
#2 - if you load data remotely, you should't set it manually. This line is also unuseful.

All the best,
Alex


Post by alex.l »

Hi chrisb,

Regarding to the initial issue you mentioned, here is a ticket: https://github.com/bryntum/support/issues/2738
There is a bug and it should work without a workaround. You can subscribe on the updates to be notified when it fixed.

Thank you for your report and time,
Best regards,
Alex

All the best,
Alex


Post by chrisb »

Hi Alex,

Thanks for the update.

I'm still running into weird issues. On load of the store I am setting the value of the combo, however still no records show. Setting a breakpoint in the load handler, I can see I am setting the value to an array of two usernames, right after setting the value on the combo, getting the value of the combo returns an empty array.

In addition the initial load of the data seems ok in that logging out the ._data of the store shows the two complete records, but then in the next breakpoint, data seems to be an array with a single value equal to the array of the two usernames. It almost seems as if the binding to the task record is overwriting the store data, and adding an array of values as the first array value, rather than spreading the array, and in addition losing the extra data that the store provides.

I think there's definitely some issues using a multiselect combo with an ajax store, that hopefully the above issue will address. I think at the moment I am unable to use it for our intended purpose. Is there any chance of getting the fix added to a patch release? Or is there an ETA on 4.2.x being released?

Thanks,

Chris


Post by alex.l »

I am not sure about that data replacing problem. That sounds weird, I cannot reproduce that.
Did you add your fields to store model?
The solution I suggested before works to me, I tested it with some public API, please take a look:


const countryStore = new AjaxStore({
    fields : ['alpha3Code', 'name'],
    readUrl : 'https://restcountries.eu/rest/v2/all' // or copy JSON to your local server.
});

const gantt = new Gantt({
    appendTo : 'container',

features : {
    taskEdit : {
        items : {
            generalTab : {
                // change title of General tab
                title : 'Common',
                items : {
                    customCombo : {
                        type : 'combo',
                        name : 'countries',
                        label: 'Countries',
                        multiSelect: true,
                        valueField: 'alpha3Code',
                        displayField: 'name',
                        store: countryStore
                    }
                }
            },
  [....]
listeners : {
    beforeTaskEditShow({ taskRecord, editor }) {
        const { customCombo } = editor.widgetMap;
	// for test, set multi value after the data loaded using ids
        customCombo.store.on('load', () => {
            customCombo.value = ['AFG', 'ALA'];
        });
        customCombo.store.load();
    }
},

Please try it and if possible, describe clear steps to reproduce for the issue you mentioned in your last post.

All the best,
Alex

All the best,
Alex


Post by chrisb »

Hi Alex,

I'm really sorry, but I still can't get it to work. No matter what I try, I cannot get the value to display in the multiselect combo backed by an ajax store. I have other combos backed by items which work fine.

Here is the full code of my custom task editor tab

import { DealRole, DealTeam } from '@app/deals/model/deal';
import { BusinessFunction } from '@app/organization/models/business-function';
import { Role } from '@mahub/core';
import { AjaxStore, FormTab } from 'bryntum-gantt/gantt.lite.umd.js';
import { TaskEditor } from 'bryntum-gantt/gantt.umd.js';
import { flatten, uniq } from 'lodash';
import { GanttTask } from '../models/task';

export class ResourcesTab extends FormTab {

  protected static teams: DealTeam[];
  protected static dealRoles: DealRole[];
  protected static roles: Role[];
  protected static functions: BusinessFunction[];
  protected static peopleStore: AjaxStore;
  protected static roleUsernames: string[];

  public static init(teams: DealTeam[], roles: Role[], functions: BusinessFunction[]) {
    ResourcesTab.teams = teams;
    ResourcesTab.dealRoles = flatten(teams.map((t) => t.roles));
    ResourcesTab.roles = roles;
    ResourcesTab.functions = functions;
    ResourcesTab.peopleStore = new AjaxStore({
      readUrl: 'api/v3/people',
      fields : ['username', 'firstName', 'lastName', 'fullName'] as any
    });
    super.initClass();
  }

  public static initStore(task: GanttTask, editor: TaskEditor) {
    const dealRoles = ResourcesTab.dealRoles.filter((r) => (task.roleUuids || []).includes(r.uuid));
    ResourcesTab.roleUsernames = flatten(dealRoles.map((r) => r.usernames));

const { ownerPickerField } = editor.widgetMap as any;
ownerPickerField.store.on('load', () => ownerPickerField.value = task.ownerUsernames);
ownerPickerField.store.params = {
  usernames: uniq([
    ...(ResourcesTab.roleUsernames || []),
    ...task.ownerUsernames
  ])
};
ownerPickerField.store.load();
  }

  static get $name() {
    return 'HubResourcesTab';
  }

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

  static get defaultConfig() {
    return {
      title: 'Resources',
      cls: 'b-flex-row',
      defaults: {
        labelWidth: '7em'
      },
      items: {
        teamField: {
          type: 'combo',
          name: 'teamUuid',
          valueField: 'uuid',
          displayField: 'name',
          label: 'Team',
          items: ResourcesTab.teams,
          listeners: {
            change: ({ value, source }) => {
              const team = ResourcesTab.teams.find((t) => t.uuid === value);
              const func = ResourcesTab.functions.find((f) => f._id  === team.functionId);
              source.parent.widgetMap['teamFunctionField'].value = func.name;
            }
          },
          flex: '1 0 50%',
          cls: 'b-inline'
        },
        teamFunctionField: {
          type: 'readonlyfield',
          label: 'Team Function',
          flex: '1 0 50%'
        },
        roleField: {
          type: 'combo',
          name: 'roleUuids',
          valueField: 'uuid',
          multiSelect: true,
          displayField: 'name',
          label: 'Default Roles',
          items: ResourcesTab.dealRoles,
          listeners: {
            change: ({ value, source }) => {
              const dealRoles = ResourcesTab.dealRoles.filter((r) => (value || []).includes(r.uuid));
              const roles = ResourcesTab.roles.filter((r) => dealRoles.some((d) => d.roleId === r._id));
              const functions = ResourcesTab.functions.filter((f) => roles.some((r) => r.function === f._id));
              const assigned = flatten(dealRoles.map((r) => r.usernames));
              ResourcesTab.roleUsernames = assigned;
              source.parent.widgetMap['roleFunctionField'].value = functions.map((f) => f.name).join(', ');
            }
          },
          flex: '1 0 50%',
          cls: 'b-inline'
        },
        roleFunctionField: {
          type: 'readonlyfield',
          label: 'Role Functions',
          flex: '1 0 50%'
        },
        ownerPickerField: {
          type: 'combo',
          label: 'Owners',
          multiSelect: true,
          name: 'ownerUsernames',
          emptyText: 'Search for a person',
          valueField: 'username',
          displayField: 'fullName',
          minChars: 3,
          keyStrokeFilterDelay: 300,
          hideTrigger: false,
          hidePickerOnSelect: true,
          store: ResourcesTab.peopleStore,
          listItemTpl: ({ data }) => data.fullName,
          chipView: {
            itemTpl: ({ data }) => data.fullName
          },
          filterParamName: 'query',
          encodeFilterParams: (filters) => {
            return filters[0].value;
          }
        }
      }
    };
  }
}

And the call from the gantt config beforeTaskEdit listener

{
      viewPreset: 'monthAndYear',
      dependencyIdField: 'wbsValue',
      listeners: {
        beforeCellEditStart: ({ editorContext }) => {
          if (this.taskEditorService.canEditAll(editorContext.record)) {
            return true;
          }
          const canEditField = this.taskEditorService.limitedEditFields.includes(editorContext.column.field);
          return this.taskEditorService.canEditLimited(editorContext.record) && canEditField;
        },
        beforeTaskEditShow: ({ taskRecord, editor }) => {
          editor.title = this.taskEditorService.canEditLimited(taskRecord)
            || this.taskEditorService.canEditAll(taskRecord)
            ? 'Edit Task'
            : 'View Task';
          ResourcesTab.initStore(taskRecord, editor);
        }
      },
      ...
}

The ajax call response is

0: {username: "jelouie", image: "https://wwwin.cisco.com/dir/photo/zoom/jelouie.jpg",…}
1: {username: "cinchang", image: "https://wwwin.cisco.com/dir/photo/zoom/cinchang.jpg",…}
2: {username: "mahawke", image: "https://wwwin.cisco.com/dir/photo/zoom/mahawke.jpg",…}

With the below logging;

ownerPickerField.store.on('load', () => {
      console.log('Before', task.ownerUsernames, ownerPickerField.value);
      ownerPickerField.value = task.ownerUsernames;
      console.log('After', task.ownerUsernames, ownerPickerField.value);
    });

We get the following logged;

Before (3) ["jelouie", "cinchang", "mahawke"] []
After (3) ["jelouie", "cinchang", "mahawke"] []

Hope that helps,

Chris


Post by alex.l »

Thank you for the provided information! I see the problem! Unfortunately there is one more bug.
The difference via our solutions is in use filterParamName config. If it has been set, the bug appears. Here is a ticket: https://github.com/bryntum/support/issues/2775
I will try to push it with a high prio.

Thank you for your reports and clarifications!

All the best,
Alex

All the best,
Alex


Post by chrisb »

Hey guys, any update on this? It's blocking our development and release. I see it's been pushed back a couple of releases on your side.


Post Reply