Using @Html.Action in TileLayout templates

1 Answer 444 Views
TileLayout
Joe
Top achievements
Rank 1
Joe asked on 20 May 2022, 01:44 PM

I have a dashboard page made up of several different sections.  Each section is rendered via a Html.Action call.

Is it possible to rearrange these into kendo scripts so the whole page can leverage the TileLayout wrapper? 

I tried below, but it's throwing all sorts of console errors.  The child actions do contain their own javascript scripts which may be the problem perhaps?


    <script id="views-corporatedocuments-template" type="text/x-kendo-template">
        @Html.Action("CorporateDocuments")
    </script>

    <script id="views-lowmargin-template" type="text/x-kendo-template">
        @Html.Action("LowMarginOrders")
    </script>

    <script id="views-lateshipments-template" type="text/x-kendo-template">
        @Html.Action("LateShipments")
    </script>

1 Answer, 1 is accepted

Sort by
0
Eyup
Telerik team
answered on 25 May 2022, 10:10 AM

Hello Joe,

 

Thank you for writing to us.

You can try to use Partials in order to achieve this configuration:
https://www.telerik.com/forums/set-partial-view-as-containers-body-template

Feel free to give it a try and let me know about the result.

 

Regards,
Eyup
Progress Telerik

Virtual Classroom, the free self-paced technical training that gets you up to speed with Telerik and Kendo UI products quickly just got a fresh new look + new and improved content including a brand new Blazor course! Check it out at https://learn.telerik.com/.

Joe
Top achievements
Rank 1
commented on 25 May 2022, 11:51 AM

I'm having difficulty getting this to work even with only two partials.  I keep getting 'Invalid template' errors in console.

Here's how I'm calling the partial:

    <script id="views-lowmargin-template" type="text/x-kendo-template">
        @Html.Partial("_LowMarginOrders", Model.LowMarginOrders)
    </script>

 

and the actual partial view is:

@using Kendo.Mvc.UI
@model IList<LowMarginOrderModel>

<div class="ibox float-e-margins">
    <div class="ibox-content">
        <div class="table-responsive">
            <h3>Low Margin Orders</h3>
            <div class="ibox-content">
                <div class="table-responsive">
                    @(Html.Kendo().Grid(Model)
                          .Name("grd_LowMarginOrders")
                          .Columns(columns =>
                          {
                              columns.Bound(p => p.OrderNumber).Title("Order").ClientTemplate(
                                  "<a href='" +
                                  Url.Action("ViewOrder", "Orders") +
                                  "/#= OrderNumber #'" +
                                  " target='_blank'>#= OrderNumber #</a>"
                                  );
                              
                              columns.Bound(p => p.OrderDate).Title("Date").Format("{0:MM/dd/yyyy}");
                              columns.Bound(p => p.ProfitMargin).Title("Margin").Format("{0:+#;-#;0} %");
                              columns.Bound(p => p.ActualVarianceFromProfitTarget).Title("Variance").Format("{0:+#;-#;0} %");
                              columns.Command(command => { command.Destroy().Text(" "); });
                          })
                          .DataSource(dataSource => dataSource
                              .Ajax()
                              .ServerOperation(false)
                              .Model(model => model.Id(p => p.OrderNumber))
                              .Destroy(update => update.Action("LowMarginOrders_Destroy", "Dashboard"))
                          )
                          )

                </div>
            </div>
        </div>
    </div>
</div>

 

Are my partial views just not compatible with TileLayout?  I'd really rather not have to redesign all 9 of my partial views if I don't have it.

I saw another post mention:

As a workaround I instead just decided to put a placeholder in the tile and then use ajax to $.load it afterwards (no modifications needed to the ViewComponent that way).  Effectively replicating the "LoadFromUrl" feature you have in many of your other wrapper controls.  This works perfectly, I'd highly advise you to add such a feature to your tile control so I don't have to roll my own version of it.

Is this a better solution in my case?  If so, can you provide a small example snippet of how this would be done?  I am not very good at Javascript.

Eyup
Telerik team
commented on 30 May 2022, 08:25 AM

Hi Joe,

Can you also share CorporateDocuments and LateShipments Partial definitions with me?

I will then create a full working project with these files and send it to you.

Joe
Top achievements
Rank 1
commented on 30 May 2022, 10:20 AM

Here is Late Shipments:


@using Kendo.Mvc.UI
@model LateShipmentsModel

<div id="late-shipments" class="ibox float-e-margins">
    <div class="ibox-title ibox-collapsible">
        <h5>Late Shipments</h5>
    </div>
    <div class="ibox-content">
        <div class="table-responsive">
            <h3>Custom Orders</h3>
            @(Html.Kendo().Grid(Model.LateCustom)
                  .Name("grd_LateCustom")
                  .Columns(columns =>
                  {
                      columns.Bound(p => p.OrderNumber).Title("Order #")
                          .ClientTemplate(
                              "<a href='" +
                              Url.Action("ViewOrder", "Orders") +
                              "/#= OrderNumber #'" +
                              " target='_blank'>#= OrderNumber #</a>");
                      columns.Bound(p => p.ShipTo).Title("Ship To");
                      columns.Bound(p => p.WarehouseName).ClientGroupHeaderTemplate("#=value#").Hidden(true);
                  })
                  .DataSource(dataSource => dataSource
                      .Ajax()
                      .Group(groups => groups.Add(x => x.WarehouseName))
                      .ServerOperation(false)
                  )
                  .Events(events => events
                      .DataBound("onDataBound")
                    )
                  )

            <hr/>

            <h3>Stock Orders</h3>
            @(Html.Kendo().Grid(Model.LateStock)
                  .Name("grd_LateStock")
                  .Columns(columns =>
                  {
                      columns.Bound(p => p.OrderNumber).Title("Order #")
                          .ClientTemplate(
                              "<a href='" +
                              Url.Action("ViewOrder", "Orders") +
                              "/#= OrderNumber #'" +
                              " target='_blank'>#= OrderNumber #</a>");

                      columns.Bound(p => p.ShipTo).Title("Ship To");
                      columns.Bound(p => p.WarehouseName).ClientGroupHeaderTemplate("#=value#").Hidden(true);
                  })
                  .DataSource(dataSource => dataSource
                      .Ajax()
                      .Group( groups => groups.Add( x=> x.WarehouseName))
                      .ServerOperation(false)
                  )
                  .Events(events => events
                      .DataBound("onDataBound")
                  )
                  )
            
            

        </div>
    </div>
</div>

<script>
    function onDataBound(arg) {
        //var grid = $("#grd_LateCustom");
        var grid = this;
        $(".k-grouping-row").each(function (arg) {
            grid.collapseGroup(this);
        });
    }
</script>

 

And here is CorporateDocuments:


@using Kendo.Mvc.UI
@model IList<DocumentData>

<script type="text/javascript">

    function CorpDoc_uploadSuccess(e) {
        var filename = e.response.Filename;
        $("#Filename").val(filename).trigger("change");
    }

    function ddlDocumentTypeId_Change(e) {
        // get the dropdown object
        var vendorDropList = $('#DocumentTypeId').data("kendoDropDownList");

        // pull in the data object for the selected dropdown choice
        var dataItem = vendorDropList.dataItem();

        // make sure data is valid
        if (dataItem) {
            // pull in reason text, populate label
            var selectedChoice = dataItem.Value;
            updatePopup(selectedChoice);
        }
    }

    function ddlDocumentTypeId_onDataBound() {
        // get the dropdown object
        var vendorDropList = $('#DocumentTypeId').data("kendoDropDownList");

        // pull in the data object for the selected dropdown choice
        var dataItem = vendorDropList.dataItem();

        updatePopup(dataItem.Value);
    }

    function updatePopup(docTypeId) {
        if (docTypeId) {
            if (docTypeId == "0") {
                $('#divFile').show();
                $('#divCloudUrl').hide();
            } else {
                $('#divFile').hide();
                $('#divCloudUrl').show();
            }
        }
    }

    // create returns tab
    function CorporateDocuments_onRequestEnd(e) {
        //RequestEnd handler code
        if (e.type === "create" | e.type === "update" | e.type === "destroy") {
            e.sender.read();

            toastr.success("Corporate Documents updated", "Success");
        }
    }


</script>


<div class="ibox float-e-margins">
    <div class="ibox-title ibox-collapsible">
        <h5>Corporate Documents</h5>
    </div>
    <div class="ibox-content">
        <div class="table-responsive">
            @(Html.Kendo().Grid(Model)
                  .Name("grid")
                  .Columns(columns =>
                  {
                      columns.Bound(p => p.Url).ClientTemplate(
                          "# if (IsCloudUrl) { #" +
                          "#= Url #" +
                          "# } else { #" +
                          "<a href='#=Url#' target='_blank'>#= DisplayName #</a> #}#").Title("Document");

                      columns.Bound(c => c.FileDate).Format("{0:d}").Title("File Date");

                      columns.Command(command => { command.Destroy().Text(" "); });
                  })
                  .ToolBar(toolbar => toolbar.Create().Text("Add Document"))
                  .Editable(editable => editable.Mode(GridEditMode.PopUp))
                  .DataSource(dataSource => dataSource
                      .Ajax()
                      .Model(model => model.Id(p => p.Filename))
                      .Events(events => events.RequestEnd("CorporateDocuments_onRequestEnd"))
                      .Read(read => read.Action("CorpDocs_Read", "Dashboard"))
                      .Create(update => update.Action("CorpDocs_Create", "Dashboard"))
                      .Destroy(update => update.Action("CorpDocs_Destroy", "Dashboard"))
                      .ServerOperation(false))
                  .Editable(editable => editable.Mode(GridEditMode.PopUp)
                      .ConfirmDelete("Continue to delete this document/url?").DisplayDeleteConfirmation("Continue to delete this document/url?")
                      .Window( w=> w.Title("Corporate Documents").Width(600))
                      .TemplateName("_CorporateDocumentUploadEditor"))

                  )
        </div>
    </div>
</div>

 

In case you want it, here's the upload template:


@using CommerceBuilder.Common
@using CommerceBuilder.Stores
@using Kendo.Mvc.UI
@model DocumentData


<style type="text/css">
    div.k-edit-form-container {
        width: 100%;
    }

        div.k-edit-form-container div.editor-field textarea, input.k-textbox input.text-box {
            width: 95%;
            max-width: none;
        }

    .form-control, .single-line {
        width: 95% !important;
    }
</style>

@{
    StoreSettingsManager settings = AbleContext.Current.Store.Settings;
    string extensions = string.IsNullOrEmpty(settings.FileExt_Assets) ? "Any" : settings.FileExt_Assets;

    extensions = extensions.Replace(",", ", ");
    string[] validExtensions = Enumerable.ToArray(settings.FileExt_Assets.Replace(", ", ",").Split(','));
    int maxUploadSize = settings.MaxRequestLength * 1000;
    string maxUploadMessage = (settings.MaxRequestLength * 1000).ToString("N0");
}


<div class="ibox float-e-margins">
    <div class="ibox-content">
        <div class="form-group">
            <label class="tooltip-label" data-content="Specify the asset type">Document Type:</label>
            @(Html.Kendo().DropDownList()
                  .Name("DocumentTypeId")
                  .OptionLabel("Select asset type...")
                  .HtmlAttributes(new {style = "width: 80%"})
                  .BindTo(new List<SelectListItem>() {
                      new SelectListItem() {
                          Text = "Physical File",
                          Value = "0"
                      },
                      new SelectListItem() {
                          Text = "Cloud Url",
                          Value = "1"
                      }
                  })
                  .Events(e => e
                      .Change("ddlDocumentTypeId_Change")
                      .DataBound("ddlDocumentTypeId_onDataBound")
                    )
            )
        </div>
        <div id="divCloudUrl">
            <div class="form-group">
                <div class="form-group">
                    <label class="tooltip-label" data-content="Specify the display name for this resource.">Display Name:</label>
                    @Html.EditorFor(m => m.DisplayName, new { @class = "form-control", placeholder = "" })
                    @Html.ValidationMessageFor(m => m.DisplayName)
                </div>
                <div class="form-group">
                    <label class="tooltip-label" data-content="Specify the document url for this resource.">Cloud Url:</label>
                    @Html.EditorFor(m => m.Url, new { @class = "form-control", placeholder = "cloud url" })
                    @Html.ValidationMessageFor(m => m.Url)
                </div>
            </div>
        </div>
        <div id="divFile">
            <div class="form-group">
                <p>
                    <strong>Valid File Types:</strong> @extensions<br />
                    You can upload a file with a maximum size of @maxUploadMessage KB.
                </p>

                <label class="tooltip-label" data-content="Click the button to upload the resource itself.">Physical File:</label>
                @(Html.Kendo().Upload()
                  .Name("file")
                  .HtmlAttributes(new {aria_label = "file"})
                  .Multiple(false)
                  .Async(a => a
                      .Save("CorpDocUpload_Save", "Dashboard")
                      .Remove("CorpDocUpload_Remove", "Dashboard")
                      .AutoUpload(true))
                  .Events(e => e.Success("CorpDoc_uploadSuccess"))
                  .Validation(validation =>
                  {
                      validation.AllowedExtensions(@validExtensions);
                      if (@ViewBag.MaxRequestLength > 0)
                      {
                          validation.MaxFileSize(@maxUploadSize);
                      }
                  })

                  )
            </div>
            <div class="form-group">
                <label class="tooltip-label" data-content="File name of the uploaded file.">File Name:</label>
                @Html.EditorFor(m => m.Filename, new { @class = "form-control" })
                @Html.ValidationMessageFor(m => m.Filename)
            </div>
        </div>

    </div>
</div>

Eyup
Telerik team
commented on 02 Jun 2022, 08:15 AM

Hi


Eyup
Telerik team
commented on 06 Jun 2022, 07:48 AM

The Kendo Template is sensitive special symbols like #, :, %, etc. Therefore, in order to resolve the "Invalid Template" error, these symbols need to be escaped:
https://docs.telerik.com/kendo-ui/framework/templates/overview#hash-literals

Here is how to do that in your project:

                                  columns.Bound(p => p.OrderID).Title("Order").ClientTemplate(
                                      "<a href='" +
                                      Url.Action("ViewOrder", "Orders") +
                                      "/\\#= OrderID \\#'" +
                                      " target='_blank'>\\#= OrderID \\#</a>"
                                      );

                                  columns.Bound(p => p.OrderDate).Title("Date").Format("{0:MM/dd/yyyy}");
                                  columns.Bound(p => p.Freight).Title("Margin").Format("{0:p}");
                                  columns.Bound(p => p.Freight).Title("Variance").Format("{0:p}");
Also, you need to add the .ToClientTemplate() method after the Grid definitions.

And the last requirement is to take the javascript logic outside the Partial page.

The result seems pretty neat:


I am also sending the full working sample. Feel free to check it and let me know if it helps you.

Joe
Top achievements
Rank 1
commented on 15 Jun 2022, 01:08 AM

Thank you for all the work you've put into helping me figure this out.

So far, I can only get the Low Margin to render.  I'm still getting 'invalid template' on Corporate Documents even after escaping all the # symbols and forcing .ToClientTemplate() for the grid.

Question 1:  Is it possible for the data rendered in the grid to also cause the 'invalid template'?  The Corporate Documents grid will render urls which can include the ? and # symbol.  Maybe I should be encoding those values in the read event for the grid?

Question 2:  What is the exact list of symbols I need to be escaping?  I am just looking for anything # or %...are there more I need to be finding?
Eyup
Telerik team
commented on 17 Jun 2022, 09:04 AM

I am glad the provided sample has proven helpful.

As the case is getting more specific, I would kindly ask you to open a formal support ticket and send us a runnable isolated MVC sample which demonstrates the mentioned issue for further investigation.

Tags
TileLayout
Asked by
Joe
Top achievements
Rank 1
Answers by
Eyup
Telerik team
Share this question
or