Skip to content

Merge dimodi-patch-1-grid-form-template-2955 into production #2956

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 14, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
289 changes: 164 additions & 125 deletions components/grid/templates/popup-form-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ With the `FormTemplate` feature, you can customize the appearance and content of
## Specifics

When using the template, the default Popup form is replaced by the declared content within the `FormTemplate` tag. This introduces the following specifics:
* The default **Update** and **Cancel** buttons are removed. This means that the [`OnUpdate` and `OnCancel`](slug:grid-editing-overview#events) events cannot be triggered. To modify or cancel the update of a record, you need to include custom controls to manage these actions.

* The default **Update** and **Cancel** buttons are removed. This means that the [`OnUpdate` and `OnCancel`](slug:grid-editing-overview#events) events do not fire. To modify or cancel the update of a record, you need to include custom components to manage these actions.
* The popup footer remains empty by design. You can [either hide it or place your custom buttons in it](slug:grid-kb-handle-empty-popup-footer).
* The `FormTemplate` disables the [built-in validation](slug:grid-editing-validation) of the Grid. Implement a [Form Validation](slug:form-validation) instead.
* The [`<GridPopupEditFormSettings>` parameters](slug:grid-editing-popup#form-layout) do not apply to a custom `TelerikForm` that you may render inside the `<FormTemplate>` tag. Set the desired Form configurations such as `Columns`, `Orientation`, and more on the [Form component](slug:form-overview#form-parameters).
Expand All @@ -37,200 +38,238 @@ When using the template, the default Popup form is replaced by the declared cont
Using a `FormTemplate` to modify the Edit/Create Popup window.

````RAZOR
@using System.Collections.Generic;
@using System.ComponentModel.DataAnnotations
@using Telerik.DataSource
@using Telerik.DataSource.Extensions

<TelerikGrid @ref="@GridRef"
Data="@GridData"
OnRead="@OnGridRead"
TItem="@Product"
EditMode="@GridEditMode.Popup"
Pageable="true"
Width="950px"
PageSize="5"
OnDelete="@DeleteItem">
OnDelete="@OnGridDelete">
<GridToolBarTemplate>
<GridCommandButton Command="Add" Icon="@SvgIcon.Plus">Add Employee</GridCommandButton>
<GridCommandButton Command="Add">Add Item</GridCommandButton>
</GridToolBarTemplate>
<GridSettings>
<GridPopupEditSettings Width="550px" MaxHeight="95vh" MaxWidth="95vw"></GridPopupEditSettings>
<GridPopupEditFormSettings Context="FormContext">
<GridPopupEditFormSettings Context="formContext">
<FormTemplate>
@{
EditItem = FormContext.Item as Person;

<TelerikForm Model="@EditItem"
if (GridEditItem is null)
{
// Setting GridEditItem unconditionally may
// reset the modified and unsaved values after re-render.
// Nullify GridEditItem when editing completes.
GridEditItem = (Product)formContext.Item;
}

<TelerikForm Model="@GridEditItem"
ColumnSpacing="20px"
Columns="2"
ButtonsLayout="@FormButtonsLayout.Stretch"
OnValidSubmit="@OnValidSubmit">
OnValidSubmit="@OnFormValidSubmit">
<FormItems>
<FormItem Field="EmployeeId" Enabled="false"></FormItem>
<FormItem Field="Name">
</FormItem>
<FormItem Field="HireDate" LabelText="Custom Hire Date Label"></FormItem>
<FormItem>
<FormItem Field="@nameof(Product.Id)" Enabled="false" />
<FormItem Field="@nameof(Product.Name)" />
<FormItem Field="@nameof(Product.Description)"
ColSpan="2"
EditorType="@FormEditorType.TextArea" />
<FormItem Field="@nameof(Product.Price)">
<Template>
<label for="position">Custom Position Label</label>
<TelerikDropDownList Data="@PositionsData"
@bind-Value="@EditItem.Position"
Id="position">
</TelerikDropDownList>
<label class="k-label k-form-label">Price</label>
<div class="k-form-field-wrap">
<TelerikNumericTextBox @bind-Value="@GridEditItem.Price"
DebounceDelay="0" />
<TelerikValidationMessage For="@( () => GridEditItem.Price)" />
</div>
</Template>
</FormItem>
</FormItems>
<FormButtons>
<TelerikButton Icon="@nameof(SvgIcon.Save)">Save</TelerikButton>
<TelerikButton Icon="@nameof(SvgIcon.Cancel)" ButtonType="@ButtonType.Button" OnClick="@OnCancel">Cancel</TelerikButton>
</FormButtons>
</TelerikForm>
<FormItem Field="@nameof(Product.Quantity)" />
<FormItem Field="@nameof(Product.ReleaseDate)" />
<FormItem Field="@nameof(Product.Discontinued)" />
</FormItems>
<FormButtons>
<TelerikButton Icon="@nameof(SvgIcon.Save)">Save</TelerikButton>
<TelerikButton Icon="@nameof(SvgIcon.Cancel)"
ButtonType="@ButtonType.Button"
OnClick="@ExitGridEditMode">Cancel</TelerikButton>
</FormButtons>
</TelerikForm>
}
</FormTemplate>
</GridPopupEditFormSettings>
</GridSettings>
<GridColumns>
<GridColumn Field=@nameof(Person.EmployeeId) Editable="false" />
<GridColumn Field=@nameof(Person.Name) />
<GridColumn Field=@nameof(Person.HireDate) Title="Hire Date" />
<GridColumn Field=@nameof(Person.Position) Title="Position" />
<GridCommandColumn>
<GridCommandButton Command="Edit" Icon="@SvgIcon.Pencil">Edit</GridCommandButton>
<GridCommandButton Command="Delete" Icon="@SvgIcon.Trash">Delete</GridCommandButton>
<GridColumn Field="@nameof(Product.Name)" />
<GridColumn Field="@nameof(Product.Price)" DisplayFormat="{0:C2}" />
<GridColumn Field="@nameof(Product.Quantity)" DisplayFormat="{0:N0}" />
<GridColumn Field="@nameof(Product.ReleaseDate)" DisplayFormat="{0:d}" />
<GridColumn Field="@nameof(Product.Discontinued)" Width="120px" />
<GridCommandColumn Width="180px">
<GridCommandButton Command="Edit">Edit</GridCommandButton>
<GridCommandButton Command="Delete">Delete</GridCommandButton>
</GridCommandColumn>
</GridColumns>
</TelerikGrid>

@code {
private List<string> PositionsData { get; set; } = new List<string>()
{
"Manager", "Developer", "QA"
};
private ProductService GridProductService { get; set; } = new();

private TelerikGrid<Person> GridRef { get; set; }
private List<Person> GridData { get; set; }
private Person EditItem { get; set; }
private List<Person> _people;
private TelerikGrid<Product>? GridRef { get; set; }

public class Person
{
public int EmployeeId { get; set; }
public string Name { get; set; }
public DateTime HireDate { get; set; }
public string Position { get; set; }
}
private Product? GridEditItem { get; set; }

public List<Person> People
private async Task OnFormValidSubmit()
{
get
if (GridEditItem is null)
{
if (_people == null)
{
_people = GeneratePeople(30);
}
return;
}

return _people;
if (GridEditItem.Id != default)
{
await GridProductService.Update(GridEditItem);
}
else
{
await GridProductService.Create(GridEditItem);
}

await ExitGridEditMode();
}

protected override void OnInitialized()
private async Task ExitGridEditMode()
{
LoadData();
if (GridRef is null)
{
return;
}

var state = GridRef.GetState();
state.OriginalEditItem = null!;
state.EditItem = null!;
state.InsertedItem = null!;

await GridRef.SetStateAsync(state);

GridEditItem = default;
}

private void LoadData()
private async Task OnGridDelete(GridCommandEventArgs args)
{
GridData = GetPeople();
var deletedItem = (Product)args.Item;

await GridProductService.Delete(deletedItem);
}

private void DeleteItem(GridCommandEventArgs args)
private async Task OnGridRead(GridReadEventArgs args)
{
DeletePerson(args.Item as Person);
DataSourceResult result = await GridProductService.Read(args.Request);

LoadData();
args.Data = result.Data;
args.Total = result.Total;
args.AggregateResults = result.AggregateResults;
}

private async Task OnValidSubmit()
public class Product
{
public int Id { get; set; }
[Required]
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public decimal? Price { get; set; }
public int Quantity { get; set; }
[Required]
public DateTime? ReleaseDate { get; set; }
public bool Discontinued { get; set; }
}

if (EditItem.EmployeeId != default)
{
UpdatePerson(EditItem);
}
else
#region Data Service

public class ProductService
{
private List<Product> Items { get; set; } = new();

private int LastId { get; set; }

public async Task<int> Create(Product product)
{
CreatePerson(EditItem);
}
await SimulateAsyncOperation();

await ExitEditAsync();
product.Id = ++LastId;

LoadData();
}
Items.Insert(0, product);

private async Task OnCancel()
{
await ExitEditAsync();
}
return LastId;
}

private async Task ExitEditAsync()
{
var state = GridRef?.GetState();
state.OriginalEditItem = null;
state.EditItem = null;
state.InsertedItem = null;
public async Task<bool> Delete(Product product)
{
await SimulateAsyncOperation();

await GridRef?.SetStateAsync(state);
}
if (Items.Contains(product))
{
Items.Remove(product);

#region Service Methods
private List<Person> GetPeople()
{
return People;
}
return true;
}

private DataSourceResult GetPeople(DataSourceRequest request)
{
return People.ToDataSourceResult(request);
}
return false;
}

private void DeletePerson(Person person)
{
People.Remove(person);
}
public async Task<List<Product>> Read()
{
await SimulateAsyncOperation();

private void UpdatePerson(Person person)
{
var index = People.FindIndex(i => i.EmployeeId == person.EmployeeId);
if (index != -1)
return Items;
}

public async Task<DataSourceResult> Read(DataSourceRequest request)
{
People[index] = person;
return await Items.ToDataSourceResultAsync(request);
}
}

private void CreatePerson(Person person)
{
person.EmployeeId = People.Max(x => x.EmployeeId) + 1;
public async Task<bool> Update(Product product)
{
await SimulateAsyncOperation();

People.Insert(0, person);
}
int originalItemIndex = Items.FindIndex(x => x.Id == product.Id);

private List<Person> GeneratePeople(int count, int startIndex = 0)
{
List<Person> result = new List<Person>();
if (originalItemIndex != -1)
{
Items[originalItemIndex] = product;
return true;
}

return false;
}

for (int i = startIndex; i < startIndex + count; i++)
private async Task SimulateAsyncOperation()
{
result.Add(new Person()
{
EmployeeId = i,
Name = "Employee " + i.ToString(),
HireDate = new DateTime(2020, 6, 1).Date.AddDays(count - (i % 7)),
Position = i % 3 <= 2 ? PositionsData[i % 3] : PositionsData.FirstOrDefault()
await Task.Delay(100);
}

public ProductService(int itemCount = 5)
{
Random rnd = Random.Shared;

for (int i = 1; i <= itemCount; i++)
{
Items.Add(new Product()
{
Id = ++LastId,
Name = $"Product {LastId}",
Description = $"Multi-line\ndescription {LastId}",
Price = LastId % 2 == 0 ? null : rnd.Next(0, 100) * 1.23m,
Quantity = LastId % 2 == 0 ? 0 : rnd.Next(0, 3000),
ReleaseDate = DateTime.Today.AddDays(-rnd.Next(365, 3650)),
Discontinued = LastId % 2 == 0
});
}
}

return result;
}
#endregion

#endregion Data Service
}
````

Expand Down