0

now i make multiple select calendar , use blazor.

i want get callback after change date.

this is my calendar component source.

<div class="table-responsive-sm">
    <table class="table table-sm text-center calendar">
        <thead>
            <tr>
                <th colspan="7">
                    <button @onclick="(e=> ChangeMonth(-1))" class="btn btn-link">
                        &lt;
                    </button>
                    @($"{CurrentMonth:yyyy.MM}")
                    <button @onclick="(e=> ChangeMonth(1))" class="btn btn-link">
                        &gt;
                    </button>
                </th>
            </tr>
            <tr>
                <th scope="col">SUN</th>
                <th scope="col">MON</th>
                <th scope="col">TUS</th>
                <th scope="col">WED</th>
                <th scope="col">THU</th>
                <th scope="col">FRI</th>
                <th scope="col">SAT</th>
            </tr>
        </thead>
        <tbody>
            @{
                var i = 0;
                var prevLastDay = CurrentMonth.AddDays(-1).Day;
            }
            @for (var row = 0; row < 5; row++)
            {
                <tr>
                    @for (var col = 0; col < 7; col++)
                    {
                        if (i < (int)StartDayOfWeek)
                        {
                            <td style="color:gray;">
                                @(prevLastDay - ((int)StartDayOfWeek - i))
                            </td>
                        }
                        else if (i >= (DaysInMonth + (int)StartDayOfWeek))
                        {
                            <td style="color:gray;">@(i - (DaysInMonth + (int)StartDayOfWeek) + 1)</td>
                        }
                        else
                        {
                            var day = i - (int)StartDayOfWeek + 1;
                            <td>
                                <button class="btn btn-sm btn-block @(DayClass(day))" @onclick="(e=> ToggleDate(day))">
                                    @(day)
                                </button>
                            </td>
                        }
                        i++;
                    }
                </tr>
            }
        </tbody>
    </table>
</div>

@code {
    /// <summary>
    /// Current Month
    /// </summary>
    [Parameter]
    public DateTime CurrentMonth { get; set; } = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);


    /// <summary>
    /// Start Day Of First Day In Current Month
    ///</summary>
    private DayOfWeek StartDayOfWeek => CurrentMonth.DayOfWeek;

    /// <summary>
    /// Selected Day List
    /// </summary>
    [Parameter]
    public List<DateTime> SelectedDays { get; set; } = new List<DateTime>();
    [Parameter]
    public EventCallback<List<DateTime>> SelectedDaysChanged { get; set; }

    /// <summary>
    /// Selectable Day List
    /// </summary>
    [Parameter]
    public List<DateTime> SelectableDays { get; set; } = new List<DateTime>();
    [Parameter]
    public EventCallback<DateTime> CurrentMonthChanged { get; set; }


    private int DaysInMonth => DateTime.DaysInMonth(CurrentMonth.Year, CurrentMonth.Month);

    protected override void OnParametersSet()
    {
        base.OnParametersSet();
        CurrentMonth = CurrentMonth.AddDays(CurrentMonth.Day * -1 + 1);
    }

    protected override void OnInitialized()
    {
    }

    public bool IsSelectable(DateTime date)
    {
        return SelectableDays.Select(p => p.Date).Contains(date.Date);
    }

    public string DayClass(int day)
    {
        var targetDay = new DateTime(CurrentMonth.Year, CurrentMonth.Month, day);
        if (SelectedDays.Contains(targetDay))
        {
            return "btn-primary";
        }
        else if (SelectableDays.Select(p => p.Date).Contains(targetDay.Date))
        {
            return "btn-outline-primary";
        }

        return string.Empty;
    }

    public void ToggleDate(int day)
    {
        var clickedDate = new DateTime(CurrentMonth.Year, CurrentMonth.Month, day);
        if (IsSelectable(clickedDate) == false)
            return;

        if (SelectedDays.Contains(clickedDate))
        {
            SelectedDays.Remove(clickedDate);
        }
        else
        {
            SelectedDays.Add(clickedDate);
        }

        SelectedDaysChanged.InvokeAsync(SelectedDays);
    }

    public void ChangeMonth(int addMonths)
    {
        CurrentMonth = CurrentMonth.AddMonths(addMonths);
        CurrentMonthChanged.InvokeAsync(CurrentMonth);
    }
}

and. my parent page use like this.

first

<Calendar SelectableDays="SelectableDays" 
        @bind-SelectedDays="SelectedDays"
        SelectedDaysChanged="SelectedDaysChanged"
      ></Calendar>

@code{
    public List<DateTime> SelectableDays { get; set; } = new List<DateTime>() { new DateTime(2020, 04, 03) };
    public List<DateTime> SelectedDays { get; set; } = new List<DateTime>();
    public int SelectListCount { get; set; }

    public void SelectedDaysChanged(List<DateTime> selectList)
    {
        SelectListCount = selectList.Count;
    }
}

i got this message. The component parameter 'SelectedDaysChanged' is used two or more times for this component. Parameters must be unique (case-insensitive). The component parameter 'SelectedDaysChanged' is generated by the '@bind-SelectedDays' directive attribute.

so i changed my method. like this.

<Calendar SelectableDays="SelectableDays"
          @bind-SelectedDays="SelectedDays"></Calendar>

@code{
    public List<DateTime> SelectableDays { get; set; } = new List<DateTime>() { new DateTime(2020, 04, 03) };
    public List<DateTime> _selectedDays = new List<DateTime>();
    public List<DateTime> SelectedDays
    {
        get { return _selectedDays; }
        set
        {
            if (_selectedDays != value)
                _selectedDays = value;

            SelectListCount = _selectedDays.Count;
        }
    }
    public int SelectListCount { get; set; }
}

this is look like i want.

but in SelectedDays's Setter.

if (_selectedDays != value)

is always false.

this mean _selectedDays set before into this setter.

what is problem?

how can i get SelectedDays changed callback event?

i must make other property? like OnChangedSelectedDays, and call in Calendar component's ToggleDate method?

this is my full source code.

index.razor

    @page "/"


    <Calendar SelectableDays="SelectableDays"
              @bind-SelectedDays="SelectedDays"></Calendar>

    @code{
        public List<DateTime> SelectableDays { get; set; } = new List<DateTime>() { new DateTime(2020, 04, 03) };
        public List<DateTime> _selectedDays = new List<DateTime>();

        [Parameter]
        public List<DateTime> SelectedDays
        {
            get { return _selectedDays; }
            set
            {
                if (_selectedDays != value)
                    _selectedDays = value;

                SelectListCount = _selectedDays.Count;
            }
        }
        public int SelectListCount { get; set; }
    }

Calendar.razor

<div class="table-responsive-sm">
    <table class="table table-sm text-center calendar">
        <thead>
            <tr>
                <th colspan="7">
                    <button @onclick="(e=> ChangeMonth(-1))" class="btn btn-link">
                        &lt;
                    </button>
                    @($"{CurrentMonth:yyyy.MM}")
                    <button @onclick="(e=> ChangeMonth(1))" class="btn btn-link">
                        &gt;
                    </button>
                </th>
            </tr>
            <tr>
                <th scope="col">SUN</th>
                <th scope="col">MON</th>
                <th scope="col">TUS</th>
                <th scope="col">WED</th>
                <th scope="col">THU</th>
                <th scope="col">FRI</th>
                <th scope="col">SAT</th>
            </tr>
        </thead>
        <tbody>
            @{
                var i = 0;
                var prevLastDay = CurrentMonth.AddDays(-1).Day;
            }
            @for (var row = 0; row < 5; row++)
            {
                <tr>
                    @for (var col = 0; col < 7; col++)
                    {
                        if (i < (int)StartDayOfWeek)
                        {
                            <td style="color:gray;">
                                @(prevLastDay - ((int)StartDayOfWeek - i))
                            </td>
                        }
                        else if (i >= (DaysInMonth + (int)StartDayOfWeek))
                        {
                            <td style="color:gray;">@(i - (DaysInMonth + (int)StartDayOfWeek) + 1)</td>
                        }
                        else
                        {
                            var day = i - (int)StartDayOfWeek + 1;
                            <td>
                                <button class="btn btn-sm btn-block @(DayClass(day))" @onclick="(e=> ToggleDate(day))">
                                    @(day)
                                </button>
                            </td>
                        }
                        i++;
                    }
                </tr>
            }
        </tbody>
    </table>
</div>

@code {
    /// <summary>
    /// Current Month
    /// </summary>
    [Parameter]
    public DateTime CurrentMonth { get; set; } = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);


    /// <summary>
    /// Start Day Of First Day In Current Month
    ///</summary>
    private DayOfWeek StartDayOfWeek => CurrentMonth.DayOfWeek;



    // I've changed the SelectedDays property in the Calendar component
    // This is a parameter property, and it leads to subtle errors
    // when used in your code as a local variable. Instead, define a
    // local variable to get and set values from it, as I do in the
    // ToggleDate method below.
    private List<DateTime> _selectedDays = new List<DateTime>();
    [Parameter]
    public List<DateTime> SelectedDays
    {
        get { return _selectedDays; }
        set
        {
            if (_selectedDays != value)
                _selectedDays = value;

            if (SelectedDaysChanged.HasDelegate)
            {
                SelectedDaysChanged.InvokeAsync(value);
            }
        }
    }


    ///// <summary>
    ///// Selected Day List
    ///// </summary>
    //[Parameter]
    //public List<DateTime> SelectedDays { get; set; } = new List<DateTime>();
    [Parameter]
    public EventCallback<List<DateTime>> SelectedDaysChanged { get; set; }

    /// <summary>
    /// Selectable Day List
    /// </summary>
    [Parameter]
    public List<DateTime> SelectableDays { get; set; } = new List<DateTime>();
    [Parameter]
    public EventCallback<DateTime> CurrentMonthChanged { get; set; }


    private int DaysInMonth => DateTime.DaysInMonth(CurrentMonth.Year, CurrentMonth.Month);

    protected override void OnParametersSet()
    {
        base.OnParametersSet();
        CurrentMonth = CurrentMonth.AddDays(CurrentMonth.Day * -1 + 1);
    }

    protected override void OnInitialized()
    {
    }

    public bool IsSelectable(DateTime date)
    {
        return SelectableDays.Select(p => p.Date).Contains(date.Date);
    }

    public string DayClass(int day)
    {
        var targetDay = new DateTime(CurrentMonth.Year, CurrentMonth.Month, day);
        if (SelectedDays.Contains(targetDay))
        {
            return "btn-primary";
        }
        else if (SelectableDays.Select(p => p.Date).Contains(targetDay.Date))
        {
            return "btn-outline-primary";
        }

        return string.Empty;
    }

    public void ToggleDate(int day)
    {

        var clickedDate = new DateTime(CurrentMonth.Year,
                                 CurrentMonth.Month, day);
        if (IsSelectable(clickedDate) == false)
            return;
        var selectedDays = SelectedDays;
        if (selectedDays.Contains(clickedDate))
        {
            selectedDays.Remove(clickedDate);

        }
        else
        {
            selectedDays.Add(clickedDate);
        }

        // Update the SelectedDays property
        SelectedDays = selectedDays;

        //var clickedDate = new DateTime(CurrentMonth.Year, CurrentMonth.Month, day);
        //if (IsSelectable(clickedDate) == false)
        //    return;

        //if (SelectedDays.Contains(clickedDate))
        //{
        //    SelectedDays.Remove(clickedDate);
        //}
        //else
        //{
        //    SelectedDays.Add(clickedDate);
        //}

        //SelectedDaysChanged.InvokeAsync(SelectedDays);
    }

    public void ChangeMonth(int addMonths)
    {
        CurrentMonth = CurrentMonth.AddMonths(addMonths);
        CurrentMonthChanged.InvokeAsync(CurrentMonth);
    }
}

complete source.

index.razor

@page "/"


<Calendar SelectableDays="SelectableDays"
          @bind-SelectedDays="SelectedDays"></Calendar>

<div>
    @SelectListCount
</div>

@code{
    public List<DateTime> SelectableDays { get; set; } = new List<DateTime>() { new DateTime(2020, 04, 03) };
    public List<DateTime> _selectedDays = new List<DateTime>();

    [Parameter]
    public List<DateTime> SelectedDays
    {
        get { return _selectedDays; }
        set
        {
            if (_selectedDays != value)
                _selectedDays = value;

            SelectListCount = _selectedDays.Count;
        }
    }
    public int SelectListCount { get; set; }
}

calendar.razor

<div class="table-responsive-sm">
    <table class="table table-sm text-center calendar">
        <thead>
            <tr>
                <th colspan="7">
                    <button @onclick="(e=> ChangeMonth(-1))" class="btn btn-link">
                        &lt;
                    </button>
                    @($"{CurrentMonth:yyyy.MM}")
                    <button @onclick="(e=> ChangeMonth(1))" class="btn btn-link">
                        &gt;
                    </button>
                </th>
            </tr>
            <tr>
                <th scope="col">SUN</th>
                <th scope="col">MON</th>
                <th scope="col">TUS</th>
                <th scope="col">WED</th>
                <th scope="col">THU</th>
                <th scope="col">FRI</th>
                <th scope="col">SAT</th>
            </tr>
        </thead>
        <tbody>
            @{
                var i = 0;
                var prevLastDay = CurrentMonth.AddDays(-1).Day;
            }
            @for (var row = 0; row < 5; row++)
            {
                <tr>
                    @for (var col = 0; col < 7; col++)
                    {
                        if (i < (int)StartDayOfWeek)
                        {
                            <td style="color:gray;">
                                @(prevLastDay - ((int)StartDayOfWeek - i))
                            </td>
                        }
                        else if (i >= (DaysInMonth + (int)StartDayOfWeek))
                        {
                            <td style="color:gray;">@(i - (DaysInMonth + (int)StartDayOfWeek) + 1)</td>
                        }
                        else
                        {
                            var day = i - (int)StartDayOfWeek + 1;
                            <td>
                                <button class="btn btn-sm btn-block @(DayClass(day))" @onclick="@((e) => ToggleDate(day))">
                                    @(day)
                                </button>
                            </td>
                        }
                        i++;
                    }
                </tr>
            }
        </tbody>
    </table>
</div>

@code {
    /// <summary>
    /// Current Month
    /// </summary>
    [Parameter]
    public DateTime CurrentMonth { get; set; } = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);


    /// <summary>
    /// Start Day Of First Day In Current Month
    ///</summary>
    private DayOfWeek StartDayOfWeek => CurrentMonth.DayOfWeek;


    [Parameter]
    public List<DateTime> SelectedDays { get; set; }


    ///// <summary>
    ///// Selected Day List
    ///// </summary>
    //[Parameter]
    //public List<DateTime> SelectedDays { get; set; } = new List<DateTime>();
    [Parameter]
    public EventCallback<List<DateTime>> SelectedDaysChanged { get; set; }

    /// <summary>
    /// Selectable Day List
    /// </summary>
    [Parameter]
    public List<DateTime> SelectableDays { get; set; }
    [Parameter]
    public EventCallback<DateTime> CurrentMonthChanged { get; set; }


    private int DaysInMonth => DateTime.DaysInMonth(CurrentMonth.Year, CurrentMonth.Month);

    protected override void OnParametersSet()
    {
        base.OnParametersSet();
        CurrentMonth = CurrentMonth.AddDays(CurrentMonth.Day * -1 + 1);
    }

    protected override void OnInitialized()
    {
    }

    public bool IsSelectable(DateTime date)
    {
        return SelectableDays.Select(p => p.Date).Contains(date.Date);
    }

    public string DayClass(int day)
    {
        var targetDay = new DateTime(CurrentMonth.Year, CurrentMonth.Month, day);

        if (SelectedDays.Contains(targetDay))
        {
            return "btn-primary";
        }
        else if (SelectableDays.Select(p => p.Date).Contains(targetDay.Date))
        {
            return "btn-outline-primary";
        }

        return string.Empty;
    }

    public void ToggleDate(int day)
    {

        var clickedDate = new DateTime(CurrentMonth.Year,
                             CurrentMonth.Month, day);
        //if (IsSelectable(clickedDate) == false)
        //    return;

        var tempSelectedDays = SelectedDays.Select(p => p).ToList(); // add here
        if (tempSelectedDays.Contains(clickedDate))
        {
            tempSelectedDays.Remove(clickedDate);
        }
        else
        {
            tempSelectedDays.Add(clickedDate);
        }

        SelectedDaysChanged.InvokeAsync(tempSelectedDays);
    }

    public void ChangeMonth(int addMonths)
    {
        CurrentMonth = CurrentMonth.AddMonths(addMonths);
        CurrentMonthChanged.InvokeAsync(CurrentMonth);
    }
}

1 Answer 1

1

OK, this is the culprit:

//if (IsSelectable(clickedDate) == false)
    //    return;

The following code is working:

Index.razor

@page "/"


<Calendar SelectableDays="SelectableDays"
      @bind-SelectedDays="@SelectedDays"></Calendar>

@code{
      public List<DateTime> SelectableDays { get; set; } = new 
                List<DateTime>() { new DateTime(2020, 04, 03) };
      private List<DateTime> _selectedDays = new List<DateTime>();

      [Parameter]
      public List<DateTime> SelectedDays
      {
          get { return _selectedDays; }
          set
          {
             if (_selectedDays != value)
                    _selectedDays = value;
                    SelectListCount = _selectedDays.Count;
          }
      }
      public int SelectListCount { get; set; }
    }

Calendar.razor

     <div class="table-responsive-sm">
<table class="table table-sm text-center calendar">
    <thead>
        <tr>
            <th colspan="7">
                <button @onclick="(e=> ChangeMonth(-1))" class="btn btn-link">
                    &lt;
                </button>
                @($"{CurrentMonth:yyyy.MM}")
                <button @onclick="(e=> ChangeMonth(1))" class="btn btn-link">
                    &gt;
                </button>
            </th>
        </tr>
        <tr>
            <th scope="col">SUN</th>
            <th scope="col">MON</th>
            <th scope="col">TUS</th>
            <th scope="col">WED</th>
            <th scope="col">THU</th>
            <th scope="col">FRI</th>
            <th scope="col">SAT</th>
        </tr>
    </thead>
    <tbody>
        @{
            var i = 0;
            var prevLastDay = CurrentMonth.AddDays(-1).Day;
        }
        @for (var row = 0; row < 5; row++)
        {
            <tr>
                @for (var col = 0; col < 7; col++)
                {
                    if (i < (int)StartDayOfWeek)
                    {
                        <td style="color:gray;">
                            @(prevLastDay - ((int)StartDayOfWeek - i))
                        </td>
                    }
                    else if (i >= (DaysInMonth + (int)StartDayOfWeek))
                    {
                        <td style="color:gray;">@(i - (DaysInMonth + (int)StartDayOfWeek) + 1)</td>
                    }
                    else
                    {
                        var day = i - (int)StartDayOfWeek + 1;
                        <td>
                            <button class="btn btn-sm btn-block @(DayClass(day))" @onclick="@((e) => ToggleDate(day))">
                                @(day)
                            </button>
                        </td>
                    }
                    i++;
                }
            </tr>
        }
    </tbody>
 </table>
 </div>

 @code {
/// <summary>
/// Current Month
/// </summary>
[Parameter]
public DateTime CurrentMonth { get; set; } = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);


/// <summary>
/// Start Day Of First Day In Current Month
///</summary>
private DayOfWeek StartDayOfWeek => CurrentMonth.DayOfWeek;


[Parameter]
public List<DateTime> SelectedDays { get; set; }


///// <summary>
///// Selected Day List
///// </summary>
//[Parameter]
//public List<DateTime> SelectedDays { get; set; } = new List<DateTime>();
[Parameter]
public EventCallback<List<DateTime>> SelectedDaysChanged { get; set; }

/// <summary>
/// Selectable Day List
/// </summary>
[Parameter]
public List<DateTime> SelectableDays { get; set; }
[Parameter]
public EventCallback<DateTime> CurrentMonthChanged { get; set; }


private int DaysInMonth => DateTime.DaysInMonth(CurrentMonth.Year, CurrentMonth.Month);

protected override void OnParametersSet()
{
    base.OnParametersSet();
    CurrentMonth = CurrentMonth.AddDays(CurrentMonth.Day * -1 + 1);
}

protected override void OnInitialized()
{
}

public bool IsSelectable(DateTime date)
{
    return SelectableDays.Select(p => p.Date).Contains(date.Date);
}

public string DayClass(int day)
{
    var targetDay = new DateTime(CurrentMonth.Year, CurrentMonth.Month, day);

    if (SelectedDays.Contains(targetDay))
    {
        return "btn-primary";
    }
    else if (SelectableDays.Select(p => p.Date).Contains(targetDay.Date))
    {
        return "btn-outline-primary";
    }

    return string.Empty;
}

public void ToggleDate(int day)
{

    var clickedDate = new DateTime(CurrentMonth.Year,
                         CurrentMonth.Month, day);
    //if (IsSelectable(clickedDate) == false)
    //    return;

    Console.WriteLine($"ToggleDate {day}");

    if (SelectedDays.Contains(clickedDate))
    {
        SelectedDays.Remove(clickedDate);

    }
    else
    {
        SelectedDays.Add(clickedDate);
    }

    SelectedDaysChanged.InvokeAsync(SelectedDays);
}

public void ChangeMonth(int addMonths)
{
    CurrentMonth = CurrentMonth.AddMonths(addMonths);
    CurrentMonthChanged.InvokeAsync(CurrentMonth);
}
}

Nice calendar, when you finish post here the code ;)

Hope this helps..

2
  • now i post my full source code. new answer is not working T.T. infinity loop in index's SelectedDays setter..
    – LimGeomJe
    Commented Apr 3, 2020 at 11:58
  • umm thanks answer, i think this is not working,(because _selectedDays != value is alwasys false in index.razor ) but i found some trick. from your answer. thank u!. i update my question lol
    – LimGeomJe
    Commented Apr 5, 2020 at 3:28

Not the answer you're looking for? Browse other questions tagged or ask your own question.