I'm trying to create a custom control that renders a calendar in which days can be selected. I need the ability to pass an ITemplate
to the control which will be rendered for each day. The days are rendered by a Repeater.
As there is currently no existing control that allows binding to an ITemplate
(or use an ITemplate
with anything other than a collection), how do I render an ITemplate
from a controlProperty
easily?
I'd prefer to have some kind of control that just renders an ITemplate
so it can be reused elsewhere.
Partial control markup:
<!-- ... -->
<dot:Repeater ID="DaysRepeater" DataSource="{value: Days}" class="list-group list-group-flush calendar-grid">
<ItemTemplate>
<div class="{{value: "calendar-day calendar-day-" + DayOfWeekIndex }}">
<dot:LinkButton ID="DayButton" class="{{value: "list-group-item list-group-item-action " + (Selected ? "active calendar-day-btn" : "calendar-day-btn") }}"
Click="{controlCommand: SelectDate(_this.Date)}">
{{value: DayText}}
<!-- RENDER TEMPLATE HERE -->
</dot:LinkButton>
</div>
</ItemTemplate>
</dot:Repeater>
ItemTemplate in code-behind:
[MarkupOptions(AllowBinding = false, MappingMode = MappingMode.InnerElement, Required = false)]
[ConstantDataContextChange(typeof(ICollection<CalendarDayModel>)), CollectionElementDataContextChange(1)]
public ITemplate ItemTemplate
{
get { return (ITemplate)GetValue(ItemTemplateProperty)!; }
set { SetValue(ItemTemplateProperty, value); }
}
public static readonly DotvvmProperty ItemTemplateProperty =
DotvvmProperty.Register<ITemplate, Calendar>(t => t.ItemTemplate);
Example usage of control:
<cc:Calendar DataContext="{value: CalendarViewModel}" MultiSelect="true">
<ItemTemplate>
Selected: {{value: Selected}}
</ItemTemplate>
</cc:Calendar>
Unfortunately, we don't have any control in DotVVM that could bind a template and just render it. However, you can do the following trick:
PlaceHolder
control in the template of the Repeater
:<dot:Repeater ID="DaysRepeater" DataSource="{value: Days}" class="list-group list-group-flush calendar-grid">
<ItemTemplate>
<div class="{{value: "calendar-day calendar-day-" + DayOfWeekIndex}}">
<dot:LinkButton ID="DayButton" class="{{value: "list-group-item list-group-item-action " + (Selected ? "active calendar-day-btn" : "calendar-day-btn") }}"
Click="{controlCommand: SelectDate(_this.Date)}">
{{value: DayText}}
<dot:PlaceHolder ID="TemplateHost" />
</dot:LinkButton>
</div>
</ItemTemplate>
</dot:Repeater>
Repeater
template with your own one that will first render the original template, then find the placeholder, and puts the inner template inside:protected override void OnInit(IDotvvmRequestContext context)
{
var repeater = (Repeater)FindControlInContainer("DaysRepeater");
repeater!.ItemTemplate = new TemplateWrapper(ItemTemplate, repeater!.ItemTemplate);
base.OnInit(context);
}
class TemplateWrapper : ITemplate
{
private readonly ITemplate innerTemplate;
private readonly ITemplate repeaterTemplate;
public TemplateWrapper(ITemplate innerTemplate, ITemplate repeaterTemplate)
{
this.innerTemplate = innerTemplate;
this.repeaterTemplate = repeaterTemplate;
}
public void BuildContent(IDotvvmRequestContext context, DotvvmControl container)
{
repeaterTemplate.BuildContent(context, container);
var placeholder = container.FindControlInContainer("TemplateHost");
innerTemplate.BuildContent(context, placeholder!);
}
}