Skip to content

Instantly share code, notes, and snippets.

@haacked
Last active April 1, 2020 18:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save haacked/445f102e573c9c41023d55238ca8083d to your computer and use it in GitHub Desktop.
Save haacked/445f102e573c9c41023d55238ca8083d to your computer and use it in GitHub Desktop.
I would like a way to do this with the partial tag helper.

What I'm looking for is a nicer more declarative approach to Templated Razor Delegates

In Any old Razor Page or View - SomePage.cshtml

...
<div>
  <partial name="Shared/_PartialLayout" for="SomeModel">
    <strong>Content</strong> that will be injected into the layout, which 
    will get rendered here.
  </partial>
</div>
...

In _PartialLayout.cshtml

<div class="stuff">
  <h2>@Model.SubTitle</h2>
  <div class="passed-in-content">
    @RenderContent()
  </div>
</div>

And the rendered result

...
<div>
  <div class="stuff">
    <h2>@Model.SubTitle</h2>
    <div class="passed-in-content">
      <strong>Content</strong> that will be injected into the layout, which 
      will get rendered here.
    </div>
  </div>
</div>
...
@haacked
Copy link
Author

haacked commented Apr 1, 2020

The actual problem domain is I want to create reusable dialogs. I'm making use of this web component: https://github.com/github/details-dialog-element. But there's a lot of boilerplate to use it in a real app. My quick and dirty solution is to put much of that boilerplate in a layout, then create a partial per dialog invocation. The latter part is a pain.

DialogLayout.cshtml

@{
    var dialogModel = ViewData[nameof(DialogModel)] as DialogModel
        ?? throw new InvalidOperationException("Add a DialogModel to ViewData");
    var summaryCssClass = dialogModel.ButtonLabel is null
        ? null
        : "btn btn-primary";
    // If there's a button label, then the summary is a button to 
    // open the dialog. Otherwise, it's a dialog launched by some
    // other action, thus we set the tab index to -1.
    var summaryTabIndex = dialogModel.ButtonLabel is null
        ? "-1"
        : null;
    // If the dialog contains a from, we want to make sure it's tabbable,
    // otherwise we want to remove the tab index.
    var detailsTabIndex = dialogModel.ContainsForm
        ? null
        : "-1";
}

<details id="@dialogModel.Id"
    class="details-reset details-overlay details-overlay-dark">
    <summary
        tabindex="@summaryTabIndex"
        aria-label="@dialogModel.SummaryLabel"
        class="@summaryCssClass"
        role="button">@dialogModel.ButtonLabel</summary>
    <details-dialog class="anim-fade-in fast Box Box--overlay d-flex flex-column"
                    role="dialog"
                    aria-modal="true"
                    tabindex="@detailsTabIndex">
        <div class="Box-header">
            <button class="Box-btn-octicon btn-octicon float-right"
                    type="button"
                    aria-label="Close dialog"
                    data-close-dialog>
                <span class="iconify" data-icon="iwwa:delete" data-inline="false"></span>
            </button>
            <h3 class="Box-title">@dialogModel.Title</h3>
        </div>
        @if (!(dialogModel.WarningMessage is null)) {
            <div class="flash flash-full flash-error">
                <span class="iconify" data-icon="whh:warningsign" data-inline="false"></span>
                @dialogModel.WarningMessage
            </div>
        }
        <div class="Box-body overflow-auto">
            @RenderBody()
        </div>
    </details-dialog>
</details>

ChangeUsernameDialog.cshtml

@model Aboard.Boards.Pages.Settings.AccountPageModel
@{
  Layout = "Shared/Dialogs/_Layout";

  ViewData[nameof(DialogModel)] = new DialogModel {
    Id = "rename-form-dialog",
    Title = "Enter a new username",
    SummaryLabel = "Change username",
    ContainsForm = true
  };
}

<form method="post" aria-label="Change username" accept-charset="UTF-8">
  <div class="flash flash-error" asp-validation-summary="ModelOnly"></div>
  <div class="form-group">
    <input asp-for="Input.Username"
           class="form-control"
           autocomplete="off"
           spellcheck="false"
           autocapitalize="off"
           autofocus
           required
           aria-label="Username" />
    <label asp-for="Input.Username" class="note"></label>
    <span asp-validation-for="Input.Username" class="text-danger"></span>
  </div>
  <button type="submit" class="btn btn-block btn-primary" data-disable-invalid>Change my username</button>
</form>

Then when I need to invoke this.

<partial name="Shared/Dialogs/ChangeUsernameDialog" />

Lots of little challenges here. For example, this partial contains a form that posts to its containing page. So any solution needs to make that easy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment