Discuss issues related to v5.x

Post by hgx »

Hello,

I'm a frontend developper currently maintaining a Gantt application based on Bryntum's Gantt for ExtJS. This application has been developed by a freelance developer many years ago and we'd like to improve it by providing a new feature.

Our use case doesn't seem that complicated! However, none of the current team members are truly familiar with Sencha...

As a quick explanation of the feature, we'd like to edit a set of fields from a custom column with a combobox editor. The data used to fill the combobox option are fetched dynamically (using a Sencha remote store). It looks really basic, but I ran into some issues while implementing the feature caused by my lack of knowledge in Sencha... For example, I currently don't have a clue about how the grid binds its data to the combobox editor nor do I know how to define a unique combobox for each cell (I'm encountering data corruption in multiple cells so I guess I'm doing something wrong).

Maybe one of the complications we have is that we're trying to work with an object. The current Data Model of the field I want to be able to change looks like that:

{
  AvatarURL: 'someURL',
  Title: 'some username',
  Type: 'user',
  Value: 'myAwesomeUserID,
}

Here's what I managed to do so far, but I'm quite sure I can do a lot better than that:

// Creating a new store to populate with my data.
var usersStore = Ext.create('Ext.data.Store', {
    fields: ['AvatarURL', 'Title', 'Value'],
    proxy: {
        type: 'rest',
        url: this.applicationConfig.urlPrepend + 'myAwesomeURL',
        headers: {
            'Content-Type': 'application/json',
        },
        pageParam: '',
        limitParam: '',
        startParam: '',
        withCredentials: true,
    },
});

Ext.apply(config, {
    // Here I've got to use a custom renderer method because the combobox returns a string while the initial data is an object...
    renderer: function (field) {
        if (field && field.Title) {
            return (
                '<div class="user-combo-item">' +
                '<img class="user-combo-item-picture" src="' +
                field.AvatarURL +
                '" align="left">&nbsp;&nbsp;' +
                field.Title + '</div>'
            );
        }

    var user = usersStore.findRecord('Value', field);
    if (user && user !== -1) {
        return (
            '<div class="user-combo-item">' +
            '<img class="user-combo-item-picture" src="' +
            user.get('AvatarURL') +
            '" align="left">&nbsp;&nbsp;' +
            user.get('Title') + '</div>'
        );
    }

    return '';
},
editable: true,
editor: {
    xtype: 'combobox',
    displayField: 'Title',
    valueField: 'Value',
    queryMode: 'remote',
    queryParam: 'term',
    autoLoadOnValue: true,
    store: usersStore,
    matchFieldWidth: false,
    forceSelection: true,
    beforeFocus: function () {
        if (this.lastSelection && this.lastSelection[0]) {
            this.setValue(this.lastSelection.get('Value'))
        } else if (this.originalValue && this.originalValue.Value) {
            this.setValue(this.originalValue.Value);
        }
    },
    displayTpl: Ext.create(
        'Ext.XTemplate',
        '{[this.getTitle(values)]}', {
            getTitle: function (values) {
                // I know this code is a shame, but for some reason the content of my input sometimes include the whole user object as the Title...
                if (values && values[0] && values[0].Title && values[0].Title.Title) {
                    return values[0].Title.Title;
                }

                if (values && values[0] && values[0].Title) {
                    return values[0].Title;
                }
            }
        }
    ),
    listConfig: {
        getInnerTpl: function () {
            var tpl = '<div class="user-combo-item">' +
                '<img class="user-combo-item-picture" src="{AvatarURL}" align="left">&nbsp;&nbsp;' +
                '{Title}</div>';
            return tpl;
        }
    },
}
});

My principal problem is, I don't know how my grid and the combobox editor are bound together. Which implies that I haven't always consistent data to work with (sometimes I receive and object and some other times a string).

Also, I couldn't figure out how I'm supposed to fill the default value with my user's Value since the combobox here is defined for the whole column (for which reason I came up with this beforeFocus event, but it doesn't even work properly...).

So here I am, stuck with this combobox and I'd love a bit of help!

Kind regards,

Henri


Post by pmiklashevich »

Hello Henri,

My principal problem is, I don't know how my grid and the combobox editor are bound together. Which implies that I haven't always consistent data to work with (sometimes I receive and object and some other times a string).

Also, I couldn't figure out how I'm supposed to fill the default value with my user's Value since the combobox here is defined for the whole column (for which reason I came up with this beforeFocus event, but it doesn't even work properly...).

ExtJS creates an editor per column. It doesn't destroy the instance of the widget which is used for editing. When you start editing, beforeedit is fired on the grid. You can prevent editing there, or get an access to the combo/store to perform your logic. Column has getEditor method which returns the editor field, combo in your case. When the editing begins the plugin takes the value (which is taken from the record based on the dataIndex) and sets it to the editor (combo). Now need to focus on how combo works. Since you have remote store, at the time the editing begins there might be no records. If you set value that doesn't exist in the store, no records will be selected, though the value will be set anyway, as a string. There is autoLoadOnValue config which is inclined to help in such cases. Though has to be checked how it works in cell editing context. If you don't need to reload the store every time you start cell editing, I would recommend to preload the values in advance. Also you can consider making the combo queryMode local and populate the records manually.

Please modify our Advanced demo a bit to see "beforeedit" hook in action (examples/advanced/app/view/Gantt.js). This is a quick showcase.

var usersStore = Ext.create('Ext.data.Store', {
    fields: ['AvatarURL', 'Title', 'Value'],
    proxy: {
        type: 'rest',
        url: 'test',
        headers: {
            'Content-Type': 'application/json',
        },
        pageParam: '',
        limitParam: '',
        startParam: '',
        withCredentials: true,
    },
});

Ext.define('Gnt.examples.advanced.view.Gantt', {
            extend: 'Gnt.panel.Gantt',
            xtype: 'advanced-gantt',
            
listeners: { beforeedit: function(plugin, context) { var editor = context.column.getEditor(); // You can set a custom property to recognize your column here. I used "text" as a show case if (context.column.text === 'Cal ID') { editor.store.proxy.setExtraParams({ 'calId': context.value, 'recId': context.record.getId() }) } } }, requires: [ 'Gnt.column.Calendar', //...... ], //....... columns: [{ text: 'Cal ID', dataIndex: 'CalendarId', editor: { xtype: 'combobox', autoLoadOnValue: true, // other combo configs go here... store: usersStore } }, { // This is our default column for showing/editing calendars xtype: 'calendarcolumn' }, /......

Edit the Cal ID cell, see the request is sent with the extra params.

Please learn sencha docs and examples:
https://examples.sencha.com/extjs/7.0.0/examples/kitchensink/#remote-combo
https://examples.sencha.com/extjs/7.0.0/examples/kitchensink/#cell-editing
etc

Please learn how we implemented Calendar column. This might give you an idea what you can do in your case:
src/Gnt/column/Calendar.js
src/Gnt/column/mixin/TaskFieldColumn.js
src/Gnt/field/Calendar.js

Since the problem is not related to the Bryntum code, it's all around Sencha, we cannot assist you with it in scope of the forum support. But we always ready to offer you our professional services. Please check details here: https://www.bryntum.com/services/

Good luck!

Best regards,
Pavel

Pavel Miklashevich - Core Developer

Post by hgx »

Hello Pavel,

Thanks for providing such help! I'll try to find a workaround with the beforeedit event, it seems promising!

Best regards,

Henri


Post by hgx »

As a conclusion, your advices helped me a lot to finish my feature. So big thanks to you Pavel!

Accessing the context through the beforeedit event of the Gantt Panel helped me on many levels:

  • Updating the proxy settings of my editor (I also needed to update some query params depending on which row I have to update)
  • Setting the value of my combobox using my record's data
  • Figuring out how the Gantt Panel truly works and what I could optimise in my code

We also figured that we needed to operate some changes on our backend side and this is good.

Best regards,

Henri


Post Reply