Open a modal dialog from a grid with filtered data

2 Answers 234 Views
Grid Window
Chris
Top achievements
Rank 1
Iron
Iron
Iron
Chris asked on 26 Jun 2024, 03:12 PM

I am currently trialing the products to see if I can get them to do what I need for an app under development.

In my app I have a grid that lists all of a customers active products.

I want to be able to click on an item in the grid (link or button) to launch a modal dialog where I can display in depth info regarding that item - the popup itself would need to have tabstrip and perhaps one or two grids on itself.

So I obviously need to design a view to display all this info which is fine, and I assume the Window component is the most suitable to effect the popup?

I can't seem to find a concrete example of how to call the window from the initial grid, pass the parameter of the selected item, and then load the view within the window.

As I say I am new to all this so feeling my way a little and so any guidance is greatly appreciated.

Thanks

2 Answers, 1 is accepted

Sort by
0
Mihaela
Telerik team
answered on 01 Jul 2024, 08:22 AM

Hello Chris,

Indeed, you can achieve the desired result as follows:

  • Define a custom column command within the Grid's Columns() configuration. A similar example is demonstrated in the Grid Custom Command demo.
@(Html.Kendo().Grid<Product>()
    .Name("grid")
    .Columns(columns => {
        ...
        columns.Command(command => command.Custom("ViewDetails").Click("showDetails"));
    })
    ...
)

<script>
    function showDetails(e) { // The button Click event handler
  
    }
</script>
  • Define a hidden Window component in the View with the Grid:
@(Html.Kendo().Grid<Product>()
    .Name("grid")
    ...
)

@(Html.Kendo().Window()
.Name("Details")
.Title("Product Details")
.Visible(false)
.Modal(true)
.Draggable(true)
.Width(800)
)
  • Within the column command's click event handler, get a reference to the hidden Window, and call its refresh() method that will make an AJAX request to the server and load the respective View with the details.
<script>
    function showDetails(e) {
        e.preventDefault();
        var dataItem = this.dataItem($(e.currentTarget).closest("tr")); // access the data item of the respective Grid row
        var window = $("#Details").data("kendoWindow"); // get a reference to the Window
        window.refresh({ // request the View and pass the "Id" property of the data item
            url: '/Home/GetProductDetails?Id=' + dataItem.Id

        });
        window.open();
    }
</script>

//HomeController.cs
public IActionResult GetProductDetails(int Id)
{
     var gridRecord = gridDataCollection.Where(x => x.Id == Id).FirstOrDefault(); // get the Model instance
     return PartialView("_Details", record); pass it to the Partial View
}

//_Details.cshtml
@model Product

.... // View content

Let me know if this approach meets your requirements.

Regards,
Mihaela
Progress Telerik

Stay tuned by visiting our public roadmap and feedback portal pages. If you're new to the Telerik family, be sure to check out our getting started resources, as well as the only REPL playground for creating, saving, running, and sharing server-side code.
Chris
Top achievements
Rank 1
Iron
Iron
Iron
commented on 01 Jul 2024, 01:32 PM | edited

I have got a Window working using the custom command and passing the ID from the row of the grid – code below – this loads the view into the window whilst passing in the id :


<script type="text/javascript">
    function showDetails(e) {
        e.preventDefault();
        var dataItem = this.dataItem($(e.currentTarget).closest("tr"));
        var wnd = $("#Details").data("kendoWindow");
        var id = (dataItem.AssetId);
        wnd.refresh({url: "/Asset/Edit/" + id });
        wnd.center().open();
    }
</script>

@(Html.Kendo().Grid<Welcomm_CRM.Core.Assets.Asset>()
    .Name("assetsGrid")
    .Columns(columns =>
    {
        columns.Bound(c => c.AssetId).Width(140);
        columns.Bound(c => c.AssetRef).ClientTemplate("<a href='Contact/Edit/#=data.AssetId#'>#=data.AssetRef#</a>");
        columns.Bound(c => c.Username).Width(140);
        columns.Command(command => command.Custom("View Details").Click("showDetails"));
    })
    .Sortable()
    .Filterable()
    .Pageable(p => p.PageSizes(new[] { 25, 50, 100 }))
    .DataSource(assDataSource => assDataSource
    .Ajax()
    .Read(read => read.Action("CustomerAssets", "Customer", new { id = (int)Model }))
    .PageSize(25)
))

@(
Html.Kendo().Window()
            .Name("Details")
            .Title("Asset Details")
            .Content("loading asset info...")
            .Visible(false)
            .Modal(true)
            .Draggable()
            .Resizable()
        )

I have the window opening the view with a tabstrip on it also.

What I want to get to ideally is the Window remaining open until the user is finished with it and manually dismisses it, but where they can add or update data in multiple tables from one place - i.e. work their way through the tabs, and interact with the forms and grids. Is that possible?

 

 

 

 

 

Mihaela
Telerik team
commented on 03 Jul 2024, 01:51 PM

Hi Chris,

I have tested the case locally, and it appears that the Window remains open when I edit the Grid's data within a TabStrip. Would you please let me know in which cases the Window closes automatically at your end?

Best,

Mihaela

Chris
Top achievements
Rank 1
Iron
Iron
Iron
commented on 04 Jul 2024, 03:24 PM

Hi Mihaela

It is where I have a form on one of the tabs - obviosuly the postback is going to the action and clearing the window - is there something built in to post back asynchronously or similar to leave the window open, and just allow the display of an alert to say the update has taken place?

Thanks

Chris
Top achievements
Rank 1
Iron
Iron
Iron
commented on 04 Jul 2024, 04:28 PM

I found an example of AJAX postback here :

https://docs.telerik.com/aspnet-core/html-helpers/layout/form/razor-page

That seems to work in terms of passing the data to the action - just having an issue with getting it to update the success/error message on the open window - it may be because of code placement as the script to respond to the form events I had to put on the underlying view, not within the partial that is opened on the window to get that to work.

Chris
Top achievements
Rank 1
Iron
Iron
Iron
commented on 05 Jul 2024, 09:16 AM

OK have this nearly working but the script to set the success or fail messages is failing, perhaps because the element it is looking for is within the window? The error message is "Cannot read properties of undefined (reading 'find')"

I have other forms on my underlying view each with their own validation div, so I renamed them to be specific per form but that still doesn't seem to work. I can see everything else is working bar that, as I can alert box the results OK.

 

Mihaela
Telerik team
commented on 09 Jul 2024, 11:17 AM

Hi Chris,

Your assumption is correct - you need to select an element inside the Window to display the message. Would you share the View and the JS logic you use to display the message, so I can review them?

Chris
Top achievements
Rank 1
Iron
Iron
Iron
commented on 11 Jul 2024, 10:24 AM

Hi - the code as it currently stands is below - I have commented out the .find code and doing a direct .html replacement is working for me, just would be good to know why the original example code is not working. Thanks


@using Welcomm_CRM.Core.Assets;
@model Asset

<script>

    function onFormValidateField(e) {
        $("#asset-validation-success").html("");
    }

    function onFormSubmit(e) {
        e.preventDefault();
        var modelData = e.model;

        $.ajax({
            type: 'POST',
            url: "@Url.Action("AssetUpdate","Asset")",
            data: modelData,
            dataType: 'json',
            success: function (data) {
                var form = $("#assetForm").getKendoForm();
                //form.validator.validationSummary.find("ul").empty();
                //form.validator.validationSummary.addClass("k-hidden");
                $("#asset-validation-success").html("<div class='k-messagebox k-messagebox-success'>" + data.success + "</div>");
            },
            error: function (data) {
                var response = JSON.parse(data.responseText);
                var form = $("#assetForm").getKendoForm();
                var errorString = "";

                $.each(response.errors, function (key, value) {
                    errorString += '<li>' + value + '</li>';
                });
                //$("#validation-success").html("");
                //form.validator.validationSummary.find("ul").empty();
                //form.validator.validationSummary.find("ul").append(errorString);
                //form.validator.validationSummary.toggleClass("k-hidden");
                $("#asset-validation-success").html("<div class='k-messagebox k-messagebox-warning'><ul>" + errorString + "</ul></div>");
            }
        })

    }

    function onFormClear(e) {
        $("#asset-validation-success").html("");
    }
</script>

<div id="asset-validation-success"><ul></ul></div>
@(
Html.Kendo().Form<Welcomm_CRM.Core.Assets.Asset>()
        .Name("assetForm")
        .HtmlAttributes(new { action = @Url.Action("Edit", "Asset"), method = "POST" })
        .ButtonsTemplate("<button class=\"btn btn-primary k-form-submit\" type=\"submit\">Submit</button>")
        .Validatable(v =>
        {
            v.ValidateOnBlur(true);
            v.ValidationSummary(vs => vs
                .Enable(false)
                .Container("#asset-validation-success")
        );
        })
        .Layout("grid")
        .Grid(g => g.Cols(2).Gutter(10))
        .Items(items =>
        {
            items.Add()
                .Field(f => f.AssetId)
                .Editor(editor => editor.Hidden());
            items.Add()
                .Field(f => f.CustomerId)
                .Editor(editor => editor.Hidden());
            items.Add()
                .Field(f => f.Asset_Guid)
                .Editor(editor => editor.Hidden());
            items.Add()
                .Field(f => f.AppId)
                .Editor(editor => editor.Hidden());
            items.Add()
                .Field(f => f.AssetRef)
                .Label(l => l.Text("Asset Ref:"));
            items.Add()
                .Field(f => f.AssetTypeId)
                .Label(l => l.Text("Asset Type:").Optional(false))
                .Editor(e =>
                {
                    e.DropDownList()
                        .HtmlAttributes(new { })
                        .DataTextField("AssetTypeName")
                        .DataValueField("AssetTypeId")
                        .HtmlAttributes(new { style = "width:100%" })
                        .Size(ComponentSize.Small)
                        .OptionLabel("Select Type...")
                        .FillMode(FillMode.Outline)
                        .DataSource(source =>
                        {
                            source.Read(read =>
                            {
                                read.Action("AssetTypeList", "Lookup");
                            })
                            .ServerFiltering(false);
                        });
                });
            items.Add()
                .Field(f => f.Username)
                .Label(l => l.Text("Username").Optional(false));
            items.Add()
                .Field(f => f.CostCentre)
                .Label(l => l.Text("Cost Centre:").Optional(false));
            items.Add()
                .Field(f => f.Email)
                .Label(l => l.Text("Email:").Optional(false));
            items.Add()
                .Field(f => f.SimNumber)
                .Label(l => l.Text("Sim Number:").Optional(false));
            items.Add()
                .Field(f => f.SupplierId)
                .Label(l => l.Text("Supplier:").Optional(false))
                .Editor(e =>
                {
                    e.DropDownList()
                        .HtmlAttributes(new { })
                        .DataTextField("SupplierName")
                        .DataValueField("SupplierId")
                        .HtmlAttributes(new { style = "width:100%" })
                        .Size(ComponentSize.Small)
                        .OptionLabel("Select Supplier...")
                        .FillMode(FillMode.Outline)
                        .DataSource(source =>
                        {
                            source.Read(read =>
                            {
                                read.Action("SupplierList", "Lookup");
                            })
                            .ServerFiltering(false);
                        });
                });
            items.Add()
                .Field(f => f.NetworkId)
                .Label(l => l.Text("Network:").Optional(false))
                .Editor(e =>
                {
                    e.DropDownList()
                        .HtmlAttributes(new { })
                        .DataTextField("NetworkName")
                        .DataValueField("NetworkId")
                        .HtmlAttributes(new { style = "width:100%" })
                        .Size(ComponentSize.Small)
                        .OptionLabel("Select Network...")
                        .FillMode(FillMode.Outline)
                        .DataSource(source =>
                        {
                            source.Read(read =>
                            {
                                read.Action("NetworkList", "Lookup");
                            })
                            .ServerFiltering(false);
                        });
                });
            items.Add()
                .Field(f => f.Disabled)
                .Label(l => l.Text("Disabled:").Optional(false))
                .Editor(e =>
                {
                    e.CheckBox();
                });
        })
        .Events(ev => ev
            .ValidateField("onFormValidateField")
            .Submit("onFormSubmit"))
        )


 

0
Alexander
Telerik team
answered on 16 Jul 2024, 07:12 AM

Hi Chris,

My name is Alexander and I have diligently gone through the provided discussion so far. And here is my take on the matter as well.

Generally, the Telerik UI for ASP.NET Core exposes built-in PopUp Editing capabilities. Which more-or-less spares you some gymnastics. Especially when it comes to employing a Form that is associated with a given row model.

Here is a mere glimpse of what the functionality offers on the table. In terms of user experience:

 The following resources go more in-depth when it comes to employing the functionality:

Since you have mentioned that numerous tables need to be edited, I would personally advocate using the aforementioned functionality in conjunction with the hierarchical capabilities the Grid also offers. The following demo showcases such a scenario:

Notice that upon expansion the Grid rows will visualize their own TabStrips. That may have arbitrary content. In your case, the other tables can have their own PopUp editing - with their own designated forms. 

Regardless, I did try sculpturing a scenario. Which more-or-less is close to yours. However, there are some missing bits and pieces. Namely, for the TabStrip. Thus, I have assembled the scenario based on my own interpretations.

Here is what each of the proactive code compartments looks like:

Index.cshtml:

<script type="text/javascript">
    function showDetails(e) {
        e.preventDefault();
        var dataItem = this.dataItem($(e.currentTarget).closest("tr"));
        var wnd = $("#Details").data("kendoWindow");
        var id = (dataItem.AssetId);
        wnd.refresh({url: "/Asset/Edit?id=" + id });
        wnd.center().open();
    }
</script>

@(Html.Kendo().Grid<Asset>()
    .Name("assetsGrid")
    .Columns(columns =>
    {
        columns.Bound(c => c.AssetId).Width(140);
        columns.Bound(c => c.AssetRef).ClientTemplate("<a href='Contact/Edit?id=#=data.AssetId#'>#=data.AssetRef#</a>");
        columns.Bound(c => c.Username).Width(140);
        columns.Command(command => command.Custom("View Details").Click("showDetails"));
    })
    .Sortable()
    .Filterable()
    .Pageable(p => p.PageSizes(new[] { 25, 50, 100 }))
    .DataSource(assDataSource => assDataSource
        .Ajax()
        .Read(read => read.Action("CustomerAssets", "Customer"))
        .PageSize(25)
    )
)


@(Html.Kendo().Window()
    .Name("Details")
    .Title("Asset Details")
    .Content("loading asset info...")
    .Visible(false)
    .Modal(true)
    .Draggable()
    .Resizable()
)

Edit.cshtml:

@model int 

@(Html.Kendo().TabStrip()
    .Name("tabstrip")
    .Items(items =>
        items.Add().Text("Some Tab").LoadContentFrom("EditForm", "Asset", new {id = Model})
    )
)

EditForm.cshtml:

@model Asset

<script>

    function onFormValidateField(e) {
        $("#asset-validation-success").html("");
    }

    function onFormSubmit(e) {
        e.preventDefault();
        var modelData = e.model;

        $.ajax({
            type: 'POST',
            url: "@Url.Action("AssetUpdate", "Asset")",
            data: modelData,
            dataType: 'json',
            success: function (data) {
                var form = $("#assetForm").getKendoForm();
                //form.validator.validationSummary.find("ul").empty();
                //form.validator.validationSummary.addClass("k-hidden");
                $("#asset-validation-success").html("<div class='k-messagebox k-messagebox-success'>" + data.success + "</div>");
            },
            error: function (data) {
                var response = JSON.parse(data.responseText);
                var form = $("#assetForm").getKendoForm();
                var errorString = "";

                $.each(response.errors, function (key, value) {
                    errorString += '<li>' + value + '</li>';
                });
                //$("#validation-success").html("");
                //form.validator.validationSummary.find("ul").empty();
                //form.validator.validationSummary.find("ul").append(errorString);
                //form.validator.validationSummary.toggleClass("k-hidden");
                $("#asset-validation-success").html("<div class='k-messagebox k-messagebox-warning'><ul>" + errorString + "</ul></div>");
            }
        })

    }

    function onFormClear(e) {
        $("#asset-validation-success").html("");
    }
</script>

<div id="asset-validation-success"><ul></ul></div>
@(
Html.Kendo().Form<Asset>()
        .Name("assetForm")
        // Omitted for brevity...
)

AssetController.cs:

public class AssetController : Controller
{
    public IActionResult Edit(int id)
    {
        return PartialView("Edit", id);
    }

    public IActionResult EditForm(int id)
    {
        var data = Enumerable.Range(1, 10)
            .Select(i => new Asset
            {
               ...
            });

        var model = data.FirstOrDefault(model => model.AssetId == id);

        return PartialView("EditForm", model);
    }

    [HttpPost]
    public IActionResult AssetUpdate(Asset asset)
    {
        if (asset.AssetRef == null)
        {
            ModelState.AddModelError("AssetRef", "AssetRef is required");

            var errorList = (from item in ModelState
                             where item.Value.Errors.Any()
                             select item).ToDictionary(
                            kvp => kvp.Key,
                            kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).FirstOrDefault()
                        );

            Response.StatusCode = 400;
            return Json(new { errors = errorList });
        }
        return Json(new {success = "Successfully submitted form!"});
    }
}

Here is how the scenario panned out:

Attached is the sample that was previously hinted. I might be missing something here. As I did not manage to reciprocate the behavior within the sample. It would be of great help. And more than appreciated if you could consider the option of putting some muscle. By trying to replicate the behavior and send it back for further examinations.

I look forward to your reply.

Kind Regards,
Alexander
Progress Telerik

Stay tuned by visiting our public roadmap and feedback portal pages. If you're new to the Telerik family, be sure to check out our getting started resources, as well as the only REPL playground for creating, saving, running, and sharing server-side code.
Chris
Top achievements
Rank 1
Iron
Iron
Iron
commented on 16 Jul 2024, 04:03 PM

I pretty much have it working with the previous code I posted. I cannot just use the inbuilt pop up editing / grid detail in this instance as I have many related tables. I have it so the window works, loads the grids when the tabs are used, and also posts back via Ajax so that is pretty much where I need to be for now. I will however bear the grid detail in mind for some simpler scenarios I have. Thanks
Mihaela
Telerik team
commented on 18 Jul 2024, 04:24 PM

Thank you for the details, Chris.

If the issue persists at your end, feel free to modify the sample app shared by my colleague, and send it back to us for review. It would help us to diagnose the issue further and share the relevant solution.

Tags
Grid Window
Asked by
Chris
Top achievements
Rank 1
Iron
Iron
Iron
Answers by
Mihaela
Telerik team
Alexander
Telerik team
Share this question
or