Following this article: https://cpratt.co/bootstrap-4-responsive-calendar-asp-net-core-taghelper/ I was able to implement a custom Calendar tag helper which just takes a month and a year as parameters and displays the calendar for that month.
It displays a standard calendar with some days from the previous months and days in the future. What I want is to hide any rows that do not have days in the current month so essentially , some months may only have 5 rows instead of 6 as in a standard calendar.
I have attempted to hide certain cells using css , but this still gives me 6 rows with blank cells.
see the sample codepen as explained in article:
https://codepen.io/chrisdpratt/pen/OOybam As you can see there is a row with dates not in that month. I only want to display rows where there are some inclusive days in the current month.
And here is the code in a c# tag helper that renders the html.
[HtmlTargetElement("calendar", TagStructure = TagStructure.NormalOrSelfClosing)]
public class CalendarTagHelper : TagHelper
{
public int Month { get; set; }
public int Year { get; set; }
public List<CalendarEvent> Events { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "section";
output.Attributes.Add("class", "calendar");
output.Content.SetHtmlContent(GetHtml());
output.TagMode = TagMode.StartTagAndEndTag;
}
private string GetHtml()
{
var monthStart = new DateTime(Year, Month, 1);
var events = Events?.GroupBy(e => e.Date);
var html = new XDocument(
new XElement("div",
new XAttribute("class", "container-fluid"),
new XElement("header",
new XElement("h4",
new XAttribute("class", "display-4 mb-2 text-center"),
monthStart.ToString("MMMM yyyy")
),
new XElement("div",
new XAttribute("class", "row d-none d-lg-flex p-1 bg-dark text-white"),
Enum.GetValues(typeof(DayOfWeek)).Cast<DayOfWeek>().Select(d =>
new XElement("h5",
new XAttribute("class", "col-lg p-1 text-center"),
d.ToString()
)
)
)
),
new XElement("div",
new XAttribute("class", "row border border-right-0 border-bottom-0"),
GetDatesHtml()
)
)
);
return html.ToString();
IEnumerable<XElement> GetDatesHtml()
{
var startDate = monthStart.AddDays(-(int)monthStart.DayOfWeek);
var dates = Enumerable.Range(0, 42).Select(i => startDate.AddDays(i));
foreach (var d in dates)
{
if (d.DayOfWeek == DayOfWeek.Sunday && d != startDate)
{
yield return new XElement("div",
new XAttribute("class", "w-100"),
String.Empty
);
}
var mutedClasses = "d-none d-lg-inline-block bg-light text-muted";
yield return new XElement("div",
new XAttribute("class", $"day col-lg p-2 border border-left-0 border-top-0 text-truncate {(d.Month != monthStart.Month ? mutedClasses : null)}"),
new XElement("h5",
new XAttribute("class", "row align-items-center"),
new XElement("span",
new XAttribute("class", "date col-1"),
d.Day
),
new XElement("small",
new XAttribute("class", "col d-lg-none text-center text-muted"),
d.DayOfWeek.ToString()
),
new XElement("span",
new XAttribute("class", "col-1"),
String.Empty
)
),
GetEventHtml(d)
);
}
}
IEnumerable<XElement> GetEventHtml(DateTime d)
{
return events?.SingleOrDefault(e => e.Key == d)?.Select(e =>
new XElement("a",
new XAttribute("class", $"event d-block p-1 pl-2 pr-2 mb-1 rounded text-truncate small bg-{e.Type} text-white"),
new XAttribute("title", e.Title),
e.Title
)
) ?? new[] {
new XElement("p",
new XAttribute("class", "d-lg-none"),
"No events"
)
};
}
}
}
I know I need to tweak this line
var dates = Enumerable.Range(0, 42).Select(i => startDate.AddDays(i));
to get the right range so that rows without days in current month do not get rendered at all but I can't seem to wrap my head around it.
You just need to determine how many days to show based on the number of needed weeks, i.e. 42 (days) is for 6 weeks. I just went with a standard 42, because I wanted the calendar to always maintain the same visual height.
Anyways, to calculate the number of weeks to show, you can use something like:
var weeks = (int)Math.Ceiling((DateTime.DaysInMonth(Year, Month) + (int)monthStart.DayOfWeek) / 7f);
Then, just replace the dates
initialization with:
var dates = Enumerable.Range(0, weeks * 7).Select(i => startDate.AddDays(i));