Kerminator1973 / bbTableCallback

"Saving the grid's sort state as part of GridSetting" feature
1 stars 0 forks source link

Как можно установить условие отбора "Диапазон дат" на поле типа DateOnly #2

Open Kerminator1973 opened 6 months ago

Kerminator1973 commented 6 months ago

Начальная точка поиска - свойство SortKeySelector.

Скачал исходники Blazor Bootstrap, начинаю исследование кода.

Kerminator1973 commented 6 months ago

Типовое использование:

SortKeySelector="item => item.DOJ"

Определение:

/// <summary>
/// Selector function of sorting key. To be used for automatic in-memory sorting.
/// </summary>
public Expression<Func<TItem, IComparable>> SortKeySelector { get; }
Kerminator1973 commented 6 months ago

"blazorbootstrap\blazorbootstrap\Models\SortingItem.cs" можно исключать из рассмотрения, т.к. это просто контейнер свойств (sealed).

Kerminator1973 commented 6 months ago

С высокой вероятностью, смотреть нужно реализацию класса GridColumn:

public partial class GridColumn<TItem> : BlazorBootstrapComponentBase

Перспективными для анализа кажется private RenderFragment? headerTemplate; с которым связан публичный параметр HeaderContent.

Однако, в официальной документации, в разделе "Custom column headers" указывается, что когда мы определяем содержимое заголовка, фильтрация и сортировка отключаются в колонке. Т.е. это совсем не то, что нам нужно.

Kerminator1973 commented 6 months ago

Код, в котором определены условия отбора:

    public static IEnumerable<FilterOperatorInfo> GetDateFilterOperators()
    {
        List<FilterOperatorInfo> result = new()
                                          {
                                              new FilterOperatorInfo("=", "Equals", FilterOperator.Equals),
                                              new FilterOperatorInfo("!=", "Not equals", FilterOperator.NotEquals),
                                              new FilterOperatorInfo("<", "Less than", FilterOperator.LessThan),
                                              new FilterOperatorInfo("<=", "Less than or equals", FilterOperator.LessThanOrEquals),
                                              new FilterOperatorInfo(">", "Greater than", FilterOperator.GreaterThan),
                                              new FilterOperatorInfo(">=", "Greater than or equals", FilterOperator.GreaterThanOrEquals),
                                              new FilterOperatorInfo("x", "Clear", FilterOperator.Clear)
                                          };

        return result;
    }
Kerminator1973 commented 6 months ago

Решение проблемы выглядит следующим: создаются два невидимых поля с датой и для этих полей устанавливается фильтры: для первого - Greater than or equals, а для второго - Less than or equals. Третье поле с датами делается видимым, но по нему блокируется фильтрация. Наша задача, устанавливать условия отбора данных в невидимых полях по значениям во внешних полях.

Kerminator1973 commented 6 months ago

Попробовать скрыть колонку можно установив ColumnClass в hidden значение.

Нужно научиться устанавливать свойства заголовка Grid-а и его отдельных колонок. Нас интересуют FilterValue

    <GridColumn TItem="Employee1"
                HeaderText="Active"
                PropertyName="IsActive"
                SortKeySelector="item => item.IsActive"
                ColumnClass="@(x => x.IsActive ? "table-success" : "table-danger")">

Вот ещё перспективное поле RowClass.

Kerminator1973 commented 6 months ago

Мы можем спрятать колонку таблицы через F12 Developer Console, если установить свойство у th: style="display:none, то скроется только заголовок колонки.

Если выполнить вот такой код:

.column-hidden {
    visibility: collapse
}
<GridColumn TItem="Employee1" 
    ColumnClass="@(x => true ? "column-hidden" : "column-hidden")" 
    HeaderText="DOJ" PropertyName="DOJ" SortKeySelector="item => item.DOJ">

То скроется только колонка, но без заголовка и блока фильтрации.

Такое ощущение, что ColumnClass применяется только к ячейкам с данными.

Kerminator1973 commented 6 months ago

Проблема в том, что у Grid есть два блока th для каждой колонки. Одна для названия заголовка, а вторая для условия - они находятся в разным tr. Сами данные - в td.

Однако, вот такой вариант сработает успешно (нужно будет только добавить стиль конкретной таблицы, чтобы не прятать всё):

tr > *:nth-child(4) {
    display: none;
}
Kerminator1973 commented 6 months ago

Вот такое комбинированное решение работает:

tr > *:nth-child(5) {
    display: none;
}

tr > *:nth-child(6) {
    display: none;
}
    <GridColumn TItem="Employee1" Filterable="false" HeaderText="DOJ" PropertyName="DOJ" SortKeySelector="item => item.DOJ">
        @context.DOJ
    </GridColumn>
    <GridColumn TItem="Employee1" FilterOperator="FilterOperator.LessThanOrEquals" FilterValue="1995.1.1" HeaderText="DOJ" PropertyName="DOJ" SortKeySelector="item => item.DOJ">
        @context.DOJ
    </GridColumn>
    <GridColumn TItem="Employee1" FilterOperator="FilterOperator.GreaterThanOrEquals" FilterValue="1980.1.1" HeaderText="DOJ" PropertyName="DOJ" SortKeySelector="item => item.DOJ">
        @context.DOJ
    </GridColumn>

Нужно только придумать, как устанавливать FilterValue динамически.

Kerminator1973 commented 6 months ago

Инициализация фильтра внешним параметром работает (см. @beginDataFilter), но я пока не понимаю, как сделать динамическую привязку...

<div class="mb-3">
    <strong>Begin Date:</strong>:
</div>
<div class="mb-3">
    <DateInput TValue="DateOnly" @bind-Value="@BeginDate" Placeholder="Enter Date" />
    <Button Color="ButtonColor.Primary" Size="Size.Medium" @onclick="ChangeFilter">Set Filter</Button>
</div>

@code {

    public DateOnly BeginDate = DateOnly.FromDateTime(DateTime.Now.AddYears(-64));

    private void ChangeFilter() {

        // Устанавливаем данные фильтра начала даты пересчёта
        beginDataFilter = BeginDate.ToString("yyyy/MM/dd");

        // В GridColumn есть private member:
        //private async Task OnFilterValueChangedAsync(ChangeEventArgs args)
        //<input class="form-control" style="@filterStyle" type="datetime-local" value="@filterValue" @oninput="@(async args => await OnFilterValueChangedAsync(args))"/>

        // FilterValue - Это публичный параметр. Если его найти, то его можно изменить
    }

    private string beginDataFilter = "1980.1.1";
}
    <GridColumn TItem="Employee1" FilterOperator="FilterOperator.GreaterThanOrEquals" FilterValue="@beginDataFilter" HeaderText="DOJ" PropertyName="DOJ" SortKeySelector="item => item.DOJ">
        @context.DOJ
    </GridColumn>
Kerminator1973 commented 6 months ago

Используя JS-код можно установить значение поля, но это не повлияет на фильтрацию (экспериментально доказано):

<script>
    window.updateFilterValue = (filterValue) => {
        console.log('Filter value: ' + filterValue);

        const elem = document.querySelector("table>thead>tr:nth-child(2)>th:nth-child(2)>div>input");
        console.dir(elem);
        if (elem) {
            elem.value = filterValue;
        }
    };
</script>

@code {

    public DateOnly BeginDate = DateOnly.FromDateTime(DateTime.Now.AddYears(-64));

    private async void ChangeFilter() {

        // Устанавливаем данные фильтра начала даты пересчёта
        beginDataFilter = BeginDate.ToString("yyyy/MM/dd");

        // В GridColumn есть private member:
        //private async Task OnFilterValueChangedAsync(ChangeEventArgs args)
        //<input class="form-control" style="@filterStyle" type="datetime-local" value="@filterValue" @oninput="@(async args => await OnFilterValueChangedAsync(args))"/>

        // FilterValue - Это публичный параметр. Если его найти, то его можно изменить

        await JS.InvokeVoidAsync("updateFilterValue", beginDataFilter);
    }

    private string beginDataFilter = "1980.1.1";
}
Kerminator1973 commented 6 months ago

Получить доступ к фильтрам можно через GridSetting, но только на чтение, не ко всем полям и только при изменении какого-то другого фильтра:

private async Task OnGridSettingsChanged(GridSettings settings)
{
        Settings = settings;
}
Kerminator1973 commented 6 months ago

JSON, с которым умеет работать Settings, может выглядеть так:

{
  "PageNumber": 1,
  "PageSize": 8,
  "Filters": [
    {
      "PropertyName": "Name",
      "Value": "Ron",
      "Operator": 7,
      "StringComparison": 5
    },
    {
      "PropertyName": "DOJ",
      "Value": "1995.1.1",
      "Operator": 4,
      "StringComparison": 5
    },
    {
      "PropertyName": "DOJ",
      "Value": "1980.1.1",
      "Operator": 6,
      "StringComparison": 5
    }
  ]
}
Kerminator1973 commented 6 months ago

Работает вот такой вариант:

<script>
    window.updateFilterValue = (filterValue) => {
        console.log('Filter value: ' + filterValue);

        const elem = document.querySelector("table>thead>tr:nth-child(2)>th:nth-child(2)>div>input");
        if (elem) {
            elem.value = filterValue;

            var event = new Event('input', {
                bubbles: true,
            });

            elem.dispatchEvent(event);
        }
    };
</script>
Kerminator1973 commented 6 months ago

Если мы не используем дату, для видимого поля всё работает как нужно:

<div class="mb-3">
    <strong>Begin Date:</strong>:
</div>
<div class="mb-3">
    <input TValue="string" @bind-value="@BeginDate" Placeholder="Enter Date" />
    <Button Color="ButtonColor.Primary" Size="Size.Medium" @onclick="ChangeFilter">Set Filter</Button>
</div>

<script>
    window.updateFilterValue = (filterValue) => {
        const elem = document.querySelector("table>thead>tr:nth-child(2)>th:nth-child(2)>div>input");
        if (elem) {

            elem.value = filterValue;

            var event = new Event('input', {
                bubbles: true,
            });

            elem.dispatchEvent(event);
        }
    };
</script>
@code {

    //public DateOnly BeginDate = DateOnly.FromDateTime(DateTime.Now.AddYears(-64));
    public String BeginDate = "";

    private async void ChangeFilter() {
           await JS.InvokeVoidAsync("updateFilterValue", BeginDate);
    }

    private string beginDataFilter = "1980.1.1";
}
Kerminator1973 commented 6 months ago

С невидимым полем всё работает отлично:

tr > *:nth-child(2) {
    display: none;
}

Т.е. проблема с датами, вероятно, в формате даты.

Kerminator1973 commented 6 months ago

Вот такой вариант работает как нужно:

@code {

    public DateOnly BeginDate = DateOnly.FromDateTime(DateTime.Now.AddYears(-64));

    private async void ChangeFilter() {

        // Устанавливаем данные фильтра начала даты пересчёта
        beginDataFilter = BeginDate.ToString("yyyy-MM-dd");

        await JS.InvokeVoidAsync("updateFilterValue", beginDataFilter);
    }

    private string beginDataFilter = "1980.1.1";
}
Kerminator1973 commented 6 months ago

В целом, задача решена.