Раскрывающиеся панели и группировка в отчетах

Раскрывающиеся панели и группировка в отчетах

Удобное представление большого объема информации – важная задача проектирования интерфейса. В этой статье будет описана группировка данных, и помещение данных в раскрывающиеся панели (collapsible panel, «спойлер») с помощью .NET Razor. Более подробно о создании отчетов можно узнать здесь.

Группировка

Для начала работы на странице отчета на вкладке Настройки отображения в блоке Макет отчета необходимо выбрать .NET Razor и перейти к вкладке. Откроется пустой лист для разметки. Нет необходимости все делать вручную, на верхней панели находится Мастер шаблона (подробней о использовании мастера можно узнать в этой статье, с помощью которого будет сгенерирован базовый код макета, который будет выглядеть примерно следующим образом:

@using EleWise.ELMA.BPM.Web.Reports.Extensions
@using System.Data

@model EleWise.ELMA.BPM.Web.Reports.Models.ReportParametersInfo

@{
    //Получение источника данных по имени
    var data = Model.DataSources["Данные"];
    //Выполнение HQL или SQL запроса, содержащегося внутри источника данных, и получение результата
    DataTable items = data.Get();
}

<style>
.list th {
    background: none repeat scroll 0 0 #666666;
    color: #FFFFFF;
    padding: 5px;
    text-align: left;
}
.list td {
    border-bottom: 1px solid #CCCCCC;
    padding: 3px 5px;
    vertical-align: middle;
}
</style>

  
<div>
<p style="font-size:20px; text-align:center;"></p>

@* Включает постраничное отображение источника данных *@
@(Html.Pager(Model, data))
@* Заголовки колонок отчета. Название колонок берутся из названий столбцов таблицы, содержащей результат выполнение источника данных *@
<table class="list" width="100%" style="margin-top: 10px; font-size: 11px;">
    <tr>
        <th scope="col">@items.Columns["Subject"]</th>
        <th scope="col">@items.Columns["Name"]</th>
        <th scope="col">@items.Columns["FullName"]</th>
    </tr>

    @* Результат выполнения источника данных представляет собой таблицу. Пробегаемся по строкам таблицы и отображаем значение столбцов *@
    @foreach (DataRow row in items.Rows)
    { 
        <tr valign="top">
            <td>
                @* Считываем значение колонки Subject из текущей строки *@
                @row["Subject"]
            </td>
            <td>
                @* Считываем значение колонки Name из текущей строки *@
                @row["Name"]
            </td>
            <td>
                @* Считываем значение колонки FullName из текущей строки *@
                @row["FullName"]
            </td>
        </tr>
    }
</table>
</div>

 Отчет же будет выглядеть примерно так:

 

В первую очередь необходимо отключить пейджинг, т.к. сортировка на разных страницах невозможна. Сделать это можно, удалив следующую конструкцию (ее легко найти по соответствующему комментарию)

@* Включает постраничное отображение источника данных *@

@(Html.Pager(Model, data))

Затем, получаемые данные необходимо отсортировать по какому-то полю. Для этого, добавьте строчку

var groupedItems = items.Rows.Cast<DataRow>().GroupBy(r => r["FullName"]);

после существующей записи:

//Выполнение HQL или SQL запроса, содержащегося внутри источника данных, и получение результата

DataTable items = data.GetAll();

Где ["FullName"] – поле по которому будет производиться группировка.

После этого, необходимо найти начало цикла для вывода данных, который выглядит следующим образом:

 

@* Результат выполнения источника данных представляет собой таблицу. Пробегаемся по строкам таблицы и отображаем значение столбцов *@

@foreach (DataRow row in items.Rows)

И изменить на:

@foreach (DataRow row in gi)

Затем найти комментарий

@* Заголовки колонок отчета. Название колонок берутся из названий столбцов таблицы, содержащей результат выполнение источника данных *@

И вставить еще один цикл под комментарием

@foreach(var gi in groupedItems)

{

Закрывающая скобка цикла должна быть в конце макета отчета, между тегами </table> и </div>

 

</table>

}

</div>

Код целиком будет выглядеть примерно так:

 @using EleWise.ELMA.BPM.Web.Reports.Extensions

@using System.Data

@model EleWise.ELMA.BPM.Web.Reports.Models.ReportParametersInfo

@{
    //Получение источника данных по имени
    var data = Model.DataSources["Данные"];
    //Выполнение HQL или SQL запроса, содержащегося внутри источника данных, и получение результата
    DataTable items = data.Get();
    var groupedItems = items.Rows.Cast<DataRow>().GroupBy(r => r["FullName"]);

}

<style>
.list th {
    background: none repeat scroll 0 0 #666666;
    color: #FFFFFF;
    padding: 5px;
    text-align: left;
}
.list td {
    border-bottom: 1px solid #CCCCCC;
    padding: 3px 5px;
    vertical-align: middle;
}
</style>

  
<div>
<p style="font-size:20px; text-align:center;"></p>


@* Заголовки колонок отчета. Название колонок берутся из названий столбцов таблицы, содержащей результат выполнение источника данных *@
@foreach(var gi in groupedItems)
{
<table class="list" width="100%" style="margin-top: 10px; font-size: 11px;">
    <tr>
        <th scope="col">@items.Columns["Subject"]</th>
        <th scope="col">@items.Columns["Name"]</th>
        <th scope="col">@items.Columns["FullName"]</th>
    </tr>

    @* Результат выполнения источника данных представляет собой таблицу. Пробегаемся по строкам таблицы и отображаем значение столбцов *@
	@foreach (DataRow row in gi)
    { 
        <tr valign="top">
            <td>
                @* Считываем значение колонки Subject из текущей строки *@
                Тема задачи
            </td>
            <td>
                @* Считываем значение колонки Name из текущей строки *@
                @row["Name"]
            </td>
            <td>
                @* Считываем значение колонки FullName из текущей строки *@
                @row["FullName"]
            </td>
        </tr>
    }
</table>
}
</div>

А отчет примерно следующий вид:

 

Как видите, данные сгруппированы по ФИО исполнителя.

 Раскрывающиеся панели

Теперь скроем группы данных в раскрывающиеся панели.

Сперва необходимо добавить пространство имен

@using EleWise.ELMA.Web.Mvc.Html

А затем: конструкцию

@(Html.CollapsiblePanel()

                      .Header(string.Format("{0}", SR.T("Панель")))

                      .SaveState(false)

                      .Expanded(false)

                      .Class("Input_Separator")

                      .Content(@<text>

</text>).Render())

Где между тегами <text> </text> будет находиться то, что мы будем скрывать, а в строке .Header(string.Format("{0}", SR.T("Панель"))) – название панелей. В данный момент это просто строка Панель, туда можно вывести, например, название объекта группировки, в нашем случае – ФИО исполнителя. Сделать это можно заменив строку Панель на

@gi.Key.ToString()

Т.е. на название ключевой колонки, по которой идет группировка. Поскольку в раскрывающихся панелях будут находиться все данные отчета, необходимо чтобы между тегами <text> </text> попала вся часть макета отвечающая за вывод данных. Поэтому первую часть

@(Html.CollapsiblePanel()

                      .Header(string.Format("{0}", SR.T("Панель")))

                      .SaveState(false)

                      .Expanded(false)

                      .Class("Input_Separator")

                      .Content(@<text>

</text>).Render())

 

необходимо вставить сразу после начала первого цикла foreach

@foreach(var gi in groupedItems)

{

@(Html.CollapsiblePanel()

                      .Header(string.Format("{0}", SR.T(@gi.Key.ToString())))

                      .SaveState(false)

                      .Expanded(false)

                      .Class("Input_Separator")

                      .Content(@<text>

 

 

а закрыть – в самом конце:

</table>

</text>).Render())

}

</div>

до закрытия цикла.

 

Код целиком будет выглядеть так:

 @using EleWise.ELMA.BPM.Web.Reports.Extensions

@using System.Data
@using EleWise.ELMA.Web.Mvc.Html
@model EleWise.ELMA.BPM.Web.Reports.Models.ReportParametersInfo

@{
    //Получение источника данных по имени
    var data = Model.DataSources["Данные"];
    //Выполнение HQL или SQL запроса, содержащегося внутри источника данных, и получение результата
    DataTable items = data.Get();
    var groupedItems = items.Rows.Cast<DataRow>().GroupBy(r => r["FullName"]);

}

<style>
.list th {
    background: none repeat scroll 0 0 #666666;
    color: #FFFFFF;
    padding: 5px;
    text-align: left;
}
.list td {
    border-bottom: 1px solid #CCCCCC;
    padding: 3px 5px;
    vertical-align: middle;
}
</style>

  
<div>
<p style="font-size:20px; text-align:center;"></p>


@* Заголовки колонок отчета. Название колонок берутся из названий столбцов таблицы, содержащей результат выполнение источника данных *@
@foreach(var gi in groupedItems)
{
@(Html.CollapsiblePanel()
                      .Header(string.Format("{0}", SR.T(@gi.Key.ToString())))
                      .SaveState(false)
                      .Expanded(false)
                      .Class("Input_Separator")
                      .Content(@<text>
<table class="list" width="100%" style="margin-top: 10px; font-size: 11px;">
    <tr>
        <th scope="col">@items.Columns["Subject"]</th>
        <th scope="col">@items.Columns["Name"]</th>
        <th scope="col">@items.Columns["FullName"]</th>
    </tr>

    @* Результат выполнения источника данных представляет собой таблицу. Пробегаемся по строкам таблицы и отображаем значение столбцов *@
	@foreach (DataRow row in gi)
    { 
        <tr valign="top">
            <td>
                @* Считываем значение колонки Subject из текущей строки *@
                Тема задачи
            </td>
            <td>
                @* Считываем значение колонки Name из текущей строки *@
                @row["Name"]
            </td>
            <td>
                @* Считываем значение колонки FullName из текущей строки *@
                @row["FullName"]
            </td>
        </tr>
    }
</table>
</text>).Render())
}
</div>

А отчет вот так:

 

Панель Петрова Алексея свернута.

Использование параметров

Вполне вероятно, что Вам захочется менять группировку прямо со страницы вызова отчета, без внесения изменений в Дизайнере. Для этого можно использовать параметры.

Для использования параметров в коде разметки макета отчета, необходимо воспользоваться разделом Код контроллера (может очень долго открываться), куда вместо закомментированных:

//result.Model.UseCustomModel = true;
//result.Model.CustomModel = new string[] {"один", "два", "три"}

добавить следующие строки:

result.Model.UseCustomModel = true;

result.Model.CustomModel = parameters;

После чего в начало макета отчета добавить:

dynamic parameters = Model.CustomModel;

Теперь через переменную parameters можно обращаться к параметрам отчета. В данном примере в качестве параметра используется выпадающий список Gruppirovka, который имеет значения: Исполнитель и Вид деятельности.

Осталось лишь добавить условия на тип группировки и название панелей

if(parameters.Gruppirovka.Value == "Вид деятельности")

{

    groupedItems = items.Rows.Cast<DataRow>().GroupBy(r => r["Name"]);

}

По умолчанию, переменная groupedItems объявляется с группировкой по ФИО

var groupedItems = items.Rows.Cast<DataRow>().GroupBy(r => r["FullName"]);

В случае, если в выпадающем списке выбрана запись Вид деятельности, группировка осуществляется по виду деятельности. А благодаря указания ключевой колонки

.Header(string.Format("{0}", SR.T(@gi.Key.ToString())))

В качестве названия для панелей, названия будут меняться автоматически.

Полный код будет выглядеть примерно так:

 

@using EleWise.ELMA.BPM.Web.Reports.Extensions
@using System.Data
@using EleWise.ELMA.Web.Mvc.Html
@model EleWise.ELMA.BPM.Web.Reports.Models.ReportParametersInfo

@{
    //Получение источника данных по имени
    var data = Model.DataSources["Данные"];
    //Выполнение HQL или SQL запроса, содержащегося внутри источника данных, и получение результата
    dynamic parameters = Model.CustomModel;
    DataTable items = data.Get();
    var groupedItems = items.Rows.Cast<DataRow>().GroupBy(r => r["FullName"]);
    if(parameters.Gruppirovka.Value == "Вид деятельности")
    {
    groupedItems = items.Rows.Cast<DataRow>().GroupBy(r => r["Name"]);
    }


}

<style>
.list th {
    background: none repeat scroll 0 0 #666666;
    color: #FFFFFF;
    padding: 5px;
    text-align: left;
}
.list td {
    border-bottom: 1px solid #CCCCCC;
    padding: 3px 5px;
    vertical-align: middle;
}
</style>

  
<div>
<p style="font-size:20px; text-align:center;"></p>


@* Заголовки колонок отчета. Название колонок берутся из названий столбцов таблицы, содержащей результат выполнение источника данных *@
@foreach(var gi in groupedItems)
{
@(Html.CollapsiblePanel()
                      .Header(string.Format("{0}", SR.T(@gi.Key.ToString())))
                      .SaveState(false)
                      .Expanded(false)
                      .Class("Input_Separator")
                      .Content(@<text>
<table class="list" width="100%" style="margin-top: 10px; font-size: 11px;">
    <tr>
        <th scope="col">@items.Columns["Subject"]</th>
        <th scope="col">@items.Columns["Name"]</th>
        <th scope="col">@items.Columns["FullName"]</th>
    </tr>

    @* Результат выполнения источника данных представляет собой таблицу. Пробегаемся по строкам таблицы и отображаем значение столбцов *@
	@foreach (DataRow row in gi)
    { 
        <tr valign="top">
            <td>
                @* Считываем значение колонки Subject из текущей строки *@
                Тема задачи
            </td>
            <td>
                @* Считываем значение колонки Name из текущей строки *@
                @row["Name"]
            </td>
            <td>
                @* Считываем значение колонки FullName из текущей строки *@
                @row["FullName"]
            </td>
        </tr>
    }
</table>
</text>).Render())
}
</div>

А отчет сгруппированный по видам деятельности так: