Our blazing fast Grid component built with pure JavaScript


Post by edonovan »

Hi, I am using the date column cell editor within a salesforce application and I have a few questions:

  1. Is it possible to customize the error message shown in the tooltip based on the error which occurs e.g. I would like to return "Must be today or earlier" if they select a future date.

  2. Is it possible to add footer buttons to the calendar component? I would like to add a 'Today' button which the user can click to select the current date, and a 'Clear' button to clear their selection.


Post by edonovan »

{
                field: "lastInteraction",
                dataType: "date",
                renderer: ({record,}) => this.hoverIconCellRenderer({data: record.data}),
                type: "date",
                finalizeCellEdit: async (props) => {
                    if (new Date(props.value) > new Date()) return false;
                    return true;
                },
                clearable: true,
                max: new Date(),
            }
            

For reference, this is my date column config. And this 'clearable' field doesn't seem to have any impact on the editor so not sure if I am placing this in the wrong place?


Post by tasnim »

Hi,

  1. Sure, for this you could use the finalizeCellEdit config function
    https://bryntum.com/products/grid/docs/api/Grid/column/Column#config-finalizeCellEdit

                finalizeCellEdit : 'up.validateStartDateEdit',
                
    // fn code async validateStartDateEdit({ grid, value }) { if (value > DateHelper.clearTime(new Date())) { return 'Must be today or earlier' } return true; },
    Screenshot 2024-03-12 112251.png
    Screenshot 2024-03-12 112251.png (36.93 KiB) Viewed 108 times
  2. Sure here is how you could achieve it

            {
                text             : 'Start',
                id               : 'start',
                type             : 'date',
                field            : 'start',
                width            : '9em',
                finalizeCellEdit : 'up.validateStartDateEdit',
                editor           : {
                    clearable : true,
                    picker    : {
                        bbar : {
                            items : {
                                custom : {
                                    type : 'button',
                                    text : 'today',
                                    onAction(props) {
                                        const { source } = props;
                                        const datepicker = source.up('datepicker');
                                        datepicker.date = DateHelper.clearTime(new Date());
                                        datepicker.owner.value = DateHelper.clearTime(new Date());
                                    }
                                }
                            }
                        }
                    }
                }
            }
    
    Screenshot 2024-03-12 112450.png
    Screenshot 2024-03-12 112450.png (43.35 KiB) Viewed 108 times

And about the clearable you'd need to add it inside of the editor

You can test these behaviors in our cell edit demo here https://bryntum.com/products/grid/examples/celledit/ (in the start column)

Please replace the code of this demo with the code below to test it

import { Widget, EventHelper, Grid, DataGenerator, DateHelper } from '../../build/grid.module.js?474871';
import shared from '../_shared/shared.module.js?474871';

// YesNo is a custom button that toggles between Yes and No on click
class YesNo extends Widget {

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

// Factoryable type name
static get type() {
    return 'yesno';
}

// Hook up a click listener during construction
construct(config) {
    // Need to pass config to super (Widget) to have things set up properly
    super.construct(config);

    // Handle click on the element
    EventHelper.on({
        element : this.element,
        click   : 'onClick',
        thisObj : this
    });
}

// Always valid, this getter is required by CellEdit feature
get isValid() {
    return true;
}

// Get current value
get value() {
    return Boolean(this._value);
}

// Set current value, updating style
set value(value) {
    this._value = value;

    this.syncInputFieldValue();
}

// Required by CellEdit feature to update display value on language locale change
// Translation is added to examples/_shared/locales/*
syncInputFieldValue() {
    const
        {
            element,
            value
        } = this;

    if (element) {
        element.classList[value ? 'add' : 'remove']('yes');
        element.innerText = value ? this.L('L{Object.Yes}') : this.L('L{Object.No}');
    }
}

// Html for this widget
template() {
    return `<button class="yesno"></button>`;
}

// Click handler
onClick() {
    this.value = !this.value;
}
}

// Register this widget type with its Factory
YesNo.initClass();

let newPlayerCount = 0;

const grid = new Grid({

appendTo : 'container',

features : {
    cellEdit : true,
    sort     : 'name',
    stripe   : true
},

// Show changed cells
showDirty : true,

async validateStartDateEdit({ grid, value }) {
    if (value > DateHelper.clearTime(new Date())) {
        return 'Must be today or earlier'
    }
    return true;
},

columns : [
    { text : 'Name', field : 'name', flex : 1 },
    {
        text   : 'Birthplace',
        field  : 'city',
        width  : '8em',
        editor : { type : 'dropdown', items : DataGenerator.cities }
    },
    { text : 'Team', field : 'team', flex : 1 },
    { text : 'Score', field : 'score', editor : 'number', width : '5em' },
    {
        text             : 'Start',
        id               : 'start',
        type             : 'date',
        field            : 'start',
        width            : '9em',
        finalizeCellEdit : 'up.validateStartDateEdit',
        editor           : {
            clearable : true,
            picker    : {
                bbar : {
                    items : {
                        custom : {
                            type : 'button',
                            text : 'today',
                            onAction(props) {
                                const { source } = props;
                                const datepicker = source.up('datepicker');
                                datepicker.date = DateHelper.clearTime(new Date());
                                datepicker.owner.value = DateHelper.clearTime(new Date());
                            }
                        }
                    }
                }
            }
        }
    },
    { text : 'Finish (readonly)', type : 'date', field : 'finish', width : '9em', editor : false },
    { text : 'Time', id : 'time', type : 'time', field : 'time', width : '10em' },
    // Column using the custom widget defined above as its editor
    {
        text     : 'Custom', // `text` gets localized automatically, is added to examples/_shared/locales/*
        field    : 'done',
        editor   : 'yesno',
        width    : '5em',
        renderer : ({ value }) => value ? YesNo.L('L{Object.Yes}') : YesNo.L('L{Object.No}')
    },
    { type : 'percent', text : 'Percent', field : 'percent', flex : 1 }
],

data : DataGenerator.generateData(50),

listeners : {
    selectionChange({ selection }) {
        removeButton.disabled = !selection.length || grid.readOnly;
    }
},

tbar : [
    {
        type        : 'button',
        ref         : 'readOnlyButton',
        text        : 'Read-only',
        tooltip     : 'Toggles read-only mode on grid',
        toggleable  : true,
        icon        : 'b-fa-square',
        pressedIcon : 'b-fa-check-square',
        onToggle    : ({ pressed }) => {
            addButton.disabled = insertButton.disabled = grid.readOnly = pressed;

            removeButton.disabled = pressed || !grid.selectedRecords.length;
        }
    },
    {
        type  : 'buttongroup',
        items : [
            {
                type     : 'button',
                ref      : 'addButton',
                icon     : 'b-fa-plus-circle',
                text     : 'Add',
                tooltip  : 'Adds a new row (at bottom)',
                onAction : () => {
                    const
                        counter = ++newPlayerCount,
                        added   = grid.store.add({
                            name : `New player ${counter}`,
                            cls  : `new_player_${counter}`
                        });

                    grid.selectedRecord = added[0];
                }
            },
            {
                type     : 'button',
                ref      : 'insertButton',
                icon     : 'b-fa-plus-square',
                text     : 'Insert',
                tooltip  : 'Inserts a new row (at top)',
                onAction : () => {
                    const
                        counter = ++newPlayerCount,
                        added   = grid.store.insert(0, {
                            name : `New player ${counter}`,
                            cls  : `new_player_${counter}`
                        });

                    grid.selectedRecord = added[0];
                }
            }
        ]
    },
    {
        type     : 'button',
        ref      : 'removeButton',
        color    : 'b-red',
        icon     : 'b-fa b-fa-trash',
        text     : 'Remove',
        tooltip  : 'Removes selected record(s)',
        disabled : true,
        onAction : () => {
            const selected = grid.selectedRecords;

            if (selected && selected.length) {
                const
                    store      = grid.store,
                    nextRecord = store.getNext(selected[selected.length - 1]),
                    prevRecord = store.getPrev(selected[0]);

                store.remove(selected);
                grid.selectedRecord = nextRecord || prevRecord;
            }
        }
    }
]
});

const { addButton, removeButton, insertButton } = grid.widgetMap;

// Show the dirty marker
grid.store.getAt(0).score = 200;

Hope it helps.

Best of luck,
Tasnim


Post by edonovan »

Thank you for your help Tasnim, this worked!


Post Reply