Declined
Last Updated: 29 Apr 2020 14:02 by ADMIN
Pierre
Created on: 26 Oct 2019 19:51
Category: Grid
Type: Bug Report
8
InCell edit mode requires a click on the Update button to store data when EditorTemplate is used

 

@GridEditMode.Incell mode does not update automatically on row change when an editortemplate is used, as below

            <GridColumn Field=@nameof(ProjectRankingInfo.Ranking.Option2) Title="@Option2Title" Width="120px" Filterable="false">
                <EditorTemplate>
                    @{
                        currentItem = context as ProjectRankingInfo.Ranking;
                        <TelerikNumericTextBox Max="10" Min="0" Step="1" @bind-Value=@currentItem.Option2 />
                    }
                </EditorTemplate>
            </GridColumn>

 

one must click the update button for the update to occur,

 

Incell works fine and updates on row change for simple grid columns

ADMIN EDIT: SOLUTION: Read the details in the following article: https://docs.telerik.com/blazor-ui/components/grid/editing/incell#notes

ADMIN EDIT: this thread is rather long and I am adding the workaround offered by René here:

<TelerikGrid Data="@GridData" OnUpdate="@UpdateHandler"> ... </TelerikGrid>

 

In GridColumn
-------------------

<EditorTemplate>
   @{
         var item = context as MyModel;
         EditedItem = item;
         <TelerikNumericTextBox @bind-Value="@item.Number" OnChange="OnChangeItemHandler">
         </Telerik.Blazor.Components.TelerikNumericTextBox>
      }
</EditorTemplate>

 

In Code-Section
----------------------

protected MyModel EditedItem { get; set; }

protected void OnChangeItemHandler()
{
       var gridCommandEventArgs = new GridCommandEventArgs
       {
            Item = EditedItem
       };

        UpdateHandler(gridCommandEventArgs);
}

protected void UpdateHandler(GridCommandEventArgs args)
{
       var myModel = (MyModel)args.Item;

      // Save myModel to DB

      .....

}
17 comments
ADMIN
Marin Bratanov
Posted on: 29 Apr 2020 14:02

Hi everyone,

We have investigated this and, as I suspected, there isn't a way for the grid to both give you full control over the template, and keep handling the editor (to close and update cells) simultaneously. The solution is to use the grid state to close the edited cell. If you only enable the Navigable parameter the grid will keep listening to the Enter keypress and will update cells (and close them). With this, you can even go closer to excel-like behavior where insertions and edits happen with the enter key only.

So, the following article was updated to provide more details about this and to offer an example you can use as base: https://docs.telerik.com/blazor-ui/components/grid/editing/incell#notes

As such, I am closing this item to the "declined" state as it is up to the application to implement and not for the grid.

 

Regards,
Marin Bratanov
Progress Telerik

Progress is here for your business, like always. Read more about the measures we are taking to ensure business continuity and help fight the COVID-19 pandemic.
Our thoughts here at Progress are with those affected by the outbreak.
ADMIN
Marin Bratanov
Posted on: 10 Mar 2020 16:11

Indeed, Doug, but then it does not let you do a proper POST. The standard <select> is not tied to a model with a fixed set of values like Blazor components are, which is where some of the key differences will come from. Plain HTML elements are rather arbitrary in the way they let you do just about anything with them and every now and then you'll get some unexpected results, while with Blazor predictability and consistency is more important.

 

Regards,
Marin Bratanov
Progress Telerik

 UI for Blazor
Doug
Posted on: 10 Mar 2020 16:08

In your snippet it does select the first item in the drop down but if you set the value parameter in the select element to something that doesn't exist in the drop down (like in the grid when the value in the cell is empty or null) it doesn't select anything:

    <form>
        <select name="testSelect" value="">
            <option value="value1">text 1</option>
            <option value="value2">text 2</option>
            <option value="value3">text 3</option>
        </select>
        <button type="submit">submit and monitor the network request to see what the select does</button>
    </form>

But it's not the end of the world. I can make it work the way you have it. It's just a learning process to understand these little differences.

Thanks for your help.

ADMIN
Marin Bratanov
Posted on: 10 Mar 2020 09:17

Hi Doug,

The default text can work with the default value of the Value type, here is its documentation where I highlighted the key points:

DefaultText - sets the hint that is shown if no other item is selected (the Value does not match any item in the data source, or is not provided at all). The item this creates has the default value for the type of the ValueField (or, thus, the Value). For example, 0 for an int, and null for an int? or string. You need to make sure that it does not match the value of an existing item in the data source. You can find examples in the Examples section in this article and in the Input Validation article.

So, this can work when the Value is null and the type is string. The default value for a string field is null, not an empty string. This is one of the reasons why I personally dislike the string.Empty "feature" so much - it acts a little bit like the default value, but it isn't. It is like the difference between a List<T> that is not initialized and a new List<T>.

The way we have implemented the DefaultText behavior achieves a few things:

  • Lets us do type detection which makes it easier for you to write code.
  • Facilitates validation otherwise you'd have to write a lot more code to get a default item (basically, you'd need an instance of the model that you need to maintain, and it would still be tied to one value, albeit arbitrary).
  • Captures the default values which is how empty fields (those that are not initialized by your data) come up from the framework.

You can use a ComboBox with CustomText=true if you want an empty input that can take arbitrary values.

On the default select behavior - it does select the first item by default, here's a basic example - it shows how the first item is selected even though I have no even defined a value for the select, and it participates in the form with that first item value. This is why our component behaves the same way - the first item gets selected unless you tell it to select a different one.

 

    <form>
        <select name="testSelect">
            <option value="value1">text 1</option>
            <option value="value2">text 2</option>
            <option value="value3">text 3</option>
        </select>
        <button type="submit">submit and monitor the network request to see what the select does</button>
    </form>

 

 

Regards,
Marin Bratanov
Progress Telerik

 UI for Blazor
Doug
Posted on: 09 Mar 2020 21:34

Thanks Marin. I think using the DefaultText parameter is going to be the simplest but I still feel like firing the ValueChanged handler before the user actually changes something is unexpected. Why does the drop down require that a value be selected in the first place? The basic "select" element in HTML doesn't have that same requirement. If the initial value is something that doesn't exist in the drop down, the drop down simply doesn't have anything selected. In the end I can get away with using DefaultText, but it would be nice if it would accept an empty string as the default.

And you're right, the insert is trickier. My current requirements don't call for an insert so I haven't played with that yet, but as you state, you wouldn't want to close the row for editing. I imagine there would be ways to manage that via OnCreate but I'll have to play with that another time.

 

ADMIN
Marin Bratanov
Posted on: 08 Mar 2020 16:42

Hello Doug,

I have added information in the editor template docs that the context is a copy of the original model (commit link for the diff).

The Save command click should not be necessary for editing, and this page can be used for Following the fix of that bug. Updating the actual grid data source is the workaround for this, as Rene suggested earlier in the thread.

The Insert operation is trickier, though - without a Save click, merely clicking the "Add" command will insert a record in the data source and it will fire the OnCreate event - and the values there will be default. Doing so would close that row for editing, however, and I do not think that would be good user experience. How would you expect that row insertion should work without a Save command click?

On a missing value and the ValueChanged event - this behavior is expected for the dropdownlist - it must always have a value (see here). Solutions for that are using its DefaultText parameter, and/or using two-way binding with the OnChang event; or using a ComboBox that allows custom text so that empty strings and nulls are valid values for it.

 

Regards,
Marin Bratanov
Progress Telerik

 UI for Blazor
Doug
Posted on: 07 Mar 2020 23:41

Marin,

Thank you for the example. I was doing a couple things wrong and now I've basically got it working but I want to make a couple comments. Apparently the context object in the EditorTemplate is a copy of the object in the model as opposed to a reference to the object in the model. I'm with Rene in the sense that I don't want to use GridCommandColumn as I don't think it's a good UX to force the user to click the edit/update buttons. I think the value the user selects should just "take" without them having to click Update.

In your example, if you pick a value in the drop down and then move off the cell without clicking Update you lose the change. The solution I came up with for that is to modify your change handler as such:

    private void MyValueChangeHandler(string theUserChoice)
    {
        CurrentlyEditedEmployee.Role = theUserChoice;

        var e = MyData.Where(s => s.ID == CurrentlyEditedEmployee.ID).FirstOrDefault();
        e.Role = theUserChoice;
    }

You need to set the Role on both objects, one to display the value in the grid, the other to set the value in the underlying model. It feels like an extra step to have to do that but it works.

Separately, if there's not an existing value in your Position column and the user clicks on a cell, the drop down shows up and fires the change handler with the first option in the drop down. So the model gets updated without the user actually selecting an option. Any way to prevent that?


ADMIN
Marin Bratanov
Posted on: 07 Mar 2020 08:04

Hello Doug,

Do you store the currently edited model in the editor template (see here) and then update its value in the ValueChanged handler (see the second example here)? With custom editors closing the editor is still likely to be a problem, though, this is what this item is for.

I am attaching a short video of the expected behavior I get right now (it is at the end of this post) and here is the code I used:

<TelerikGrid Data=@MyData EditMode="@GridEditMode.Incell" Pageable="true" Height="500px" OnUpdate="@UpdateHandler">
    <GridColumns>
        <GridColumn Field=@nameof(SampleData.ID) Editable="false" Title="ID" />
        <GridColumn Field=@nameof(SampleData.Name) Title="Name" />
        <GridColumn Field=@nameof(SampleData.Role) Title="Position">
            <EditorTemplate>
                @{
                    CurrentlyEditedEmployee = context as SampleData;
                    <TelerikDropDownList Data="@Roles" 
                                         Value="CurrentlyEditedEmployee.Role" ValueChanged="@( (string v) => MyValueChangeHandler(v) )"
                                         Width="120px" PopupHeight="auto"></TelerikDropDownList>
                }
            </EditorTemplate>
        </GridColumn>
        <GridCommandColumn>
            <GridCommandButton Command="Save" Icon="save" ShowInEdit="true">Update</GridCommandButton>
            <GridCommandButton Command="Edit" Icon="edit">Edit</GridCommandButton>
        </GridCommandColumn>
    </GridColumns>
</TelerikGrid>

@code {
    public SampleData CurrentlyEditedEmployee { get; set; }

    private void MyValueChangeHandler(string theUserChoice)
    {
        CurrentlyEditedEmployee.Role = theUserChoice;
    }

    public void UpdateHandler(GridCommandEventArgs args)
    {
        SampleData item = (SampleData)args.Item;

        var index = MyData.FindIndex(i => i.ID == item.ID);
        if (index != -1)
        {
            MyData[index] = item;
        }
    }

    protected override void OnInitialized()
    {
        MyData = new List<SampleData>();

        for (int i = 0; i < 50; i++)
        {
            MyData.Add(new SampleData()
            {
                ID = i,
                Name = "name " + i,
                Role = Roles[i % Roles.Count]
            });
        }
    }

    //in a real case, keep the models in dedicated locations, this is just an easy to copy and see example
    public class SampleData
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Role { get; set; }
    }

    public List<SampleData> MyData { get; set; }

    public static List<string> Roles = new List<string> { "Manager", "Employee", "Contractor" };
}

 

Regards,
Marin Bratanov
Progress Telerik

 UI for Blazor
Attached Files:
Doug
Posted on: 06 Mar 2020 23:08
When I use ValueChanged instead of @bind-Value as per your suggestion for the DropDownList scenario it no longer allows edit in the grid. Is this a bug or am I missing something?
ADMIN
Marin Bratanov
Posted on: 06 Dec 2019 07:20

Hi René,

The OnChange event is specific to an input - it fires when the input is blurred, or when the user presses Enter in it. A dropdownlist does not have an input and cannot expose this behavior. The same goes for a checkbox. They are not free input components, they offer selection out of predefined choices, so the potential OnChange matches exactly ValueChanged.

Thus, the solution for such scenarios is to use ValueChanged and to update the model with your own code. You can either pass the context to the handler, or store the currently edited row in a "global" variable outside of the template so you can access it in the handlers.

On a side note, for checkboxes we offer styling options that you can use: https://docs.telerik.com/blazor-ui/themes/form-elements#checkboxes.

Regards,
Marin Bratanov
Progress Telerik

 UI for Blazor
René
Posted on: 06 Dec 2019 07:11
To be able to use the workaround with bool values it would also be great to have a Checkbox or Toggle Component with an OnChange event.
René
Posted on: 06 Dec 2019 07:07
Unfortunately it does not work for TelerikDropDownList which has an OnChange event which is not fired (documentation says it is inherited and should not be used).  The ValueChanged event cannot be used if bind-value is used.  So unfortunately for dropdown lists I would need one intermediate eventhandler for each column since I would have to set the value in the valueChangedHandler.  I think it is a shame that there is such an inconsistency in the Telerik Blazor Components.  The OnChange event works fine with TextBoxes/NumberBoxes and DatePickers - why can it not work with DropDowns as well ???
ADMIN
Marin Bratanov
Posted on: 05 Dec 2019 16:12

Thank you for sharing your solution with the community, René! Indeed, this is how this problem can be worked around.

--Marin

René
Posted on: 05 Dec 2019 15:14

I have now developed the following workaround:

<TelerikGrid Data="@GridData" OnUpdate="@UpdateHandler"> ... </TelerikGrid>

 

In GridColumn
-------------------

<EditorTemplate>
   @{
         var item = context as MyModel;
         EditedItem = item;
         <TelerikNumericTextBox @bind-Value="@item.Number" OnChange="OnChangeItemHandler">
         </Telerik.Blazor.Components.TelerikNumericTextBox>
      }
</EditorTemplate>

 

In Code-Section
----------------------

protected MyModel EditedItem { get; set; }

protected void OnChangeItemHandler()
{
       var gridCommandEventArgs = new GridCommandEventArgs
       {
            Item = EditedItem
       };

        UpdateHandler(gridCommandEventArgs);
}

protected void UpdateHandler(GridCommandEventArgs args)
{
       var myModel = (MyModel)args.Item;

      // Save myModel to DB

      .....

}

 

This example is for a simple Numberbox but it will work for any control with an OnChange event.

ADMIN
Marin Bratanov
Posted on: 05 Dec 2019 14:28

Hi René,

I have raised the priority of this bug and you can click the Follow button to get status change notifications.

At this point I am not certain how this would be fixed and it may be that you'd have to call methods on the grid (that would likely be exposed through this item) to update the state of the grid when custom editors are used, because the grid can't know what happens inside the editor.

 

Regards,
Marin Bratanov
Progress Telerik

 UI for Blazor
René
Posted on: 05 Dec 2019 14:18

Will this be fixed soon?

We use a lot of EditorTemplates in Grids (e.g. dropdowns, custom number boxes, DatePickers) and tables that are wider than the screen.  The main advantage of Incell Editing over Inline Editing is just not having to click on a save button (and on a edit button before that) which might be out of your view. If the Values are not saved when leaving the cell, Incell editing is useless especially in cases the grid is wider than the screen and you have to scroll to reach the save button after editing.

Please fix this soon (or at least show a save button right next to the cell currently edited).

ADMIN
Marin Bratanov
Posted on: 28 Oct 2019 07:52

Hello Pierre,

It seems there is an issue with custom editors (the cells don't even close when you click outside of them) and I made this public so you can Follow it at this page: https://feedback.telerik.com/blazor/1436240-grideditmode-incell-does-not-update-automatically-on-row-change-when-an-editortemplate-is-used.

Consideing your sample, you may also want to Follow this item so you can use such nested models: https://feedback.telerik.com/blazor/1432615-support-for-nested-complex-models. At the moment, you'd have to flatten them, something like:

<TelerikGrid Data=@MyData EditMode="@GridEditMode.Incell" Pageable="true" Height="500px" OnUpdate="@UpdateHandler">
    <GridColumns>
        <GridColumn Field=@nameof(SampleData.ID) Editable="false" Title="ID" />
        <GridColumn Field=@nameof(SampleData.Name) Title="Name">
            <EditorTemplate>
                @{
                    currentItem = context as SampleData;
                    <TelerikTextBox Width="100%" @bind-Value=@currentItem.Name />
                }
            </EditorTemplate>
        </GridColumn>
        <GridColumn Field=@nameof(SampleData.Ranking) Title="Ranking" Width="120px" Filterable="false">
            <EditorTemplate>
                @{
                    currentItem = context as SampleData;
                    <TelerikNumericTextBox Width="100%" Max="10" Min="0" Step="1" @bind-Value=@currentItem.Ranking />
                }
            </EditorTemplate>
        </GridColumn>
        <GridCommandColumn>
            <GridCommandButton Command="Save" Icon="save" ShowInEdit="true">Update</GridCommandButton>
            <GridCommandButton Command="Edit" Icon="edit">Edit</GridCommandButton>
        </GridCommandColumn>
    </GridColumns>
</TelerikGrid>

@code {
    public SampleData currentItem { get; set; }

    public void UpdateHandler(GridCommandEventArgs args)
    {
        SampleData item = (SampleData)args.Item;

        //perform actual data source operations here
        //if you have a context added through an @inject statement, you could call its SaveChanges() method
        //myContext.SaveChanges();

        var index = MyData.FindIndex(i => i.ID == item.ID);
        if (index != -1)
        {
            MyData[index] = item;
        }
    }

    protected override void OnInitialized()
    {
        MyData = new List<SampleData>();

        for (int i = 0; i < 50; i++)
        {
            MyData.Add(new SampleData()
            {
                ID = i,
                Name = "name " + i,
                Ranking = i % 10
            });
        }
    }

    //in a real case, keep the models in dedicated locations, this is just an easy to copy and see example
    public class SampleData
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public int Ranking { get; set; }
    }

    public List<SampleData> MyData { get; set; }
}

 

Regards,
Marin Bratanov
Progress Telerik

 UI for Blazor