Технология автоматического объединения ячеек
Ячейки некоторых таблиц иногда требуется объединять
для большей наглядности и аккуратного внешнего вида. Рассмотрим, как это можно
делать, на примере журнала посещений регистратуры.
Обратите внимание, только часть столбцов содержат
объединённые по вертикали ячейки. Это столбцы "Время", "Клиент"
и "Специалист". Объединяются ячейки, имеющие одинаковое содержимое. Ячейки
в других столбцах объединять не нужно.
1. В начале функции, формирующей web-страницу журнала
посещений, описываем структуру, содержащую значения объединяемых полей. Надо
сказать, что в структуре присутствуют также данные столбца "Дата", но этот
столбец обычно не используется, поэтому на скриншоте не отображён.
string s;
int iCycle;
struct ROW
{
char szSort [1024]; // для сортировки
int iVisitNum;
int iVisit;
int iClientNum;
int iClient;
// дата
char sz1 [5 +1];
int iSpan1;
// время
char sz2 [5 +1];
int iSpan2;
// ФИО клиента
char sz3 [100];
int iSpan3;
// специалист
int i4;
int iSpan4;
};
ROW curr_row,next_row;
std::auto_ptr<RING> rows (new RING("fnAppReg(QUERY *): rows",1024*1024));
int iQty;
int iCycleRow;
|
2. В цикле производится выборка актуальных записей из базы
данных посещений для отображения в журнале. Производится заполнение
структуры curr_row и размещение этих данных в динамическом массиве rows
с начальным размером 1 мегабайт.
// ***************************
// * поиск посещений по дате *
// ***************************
for (iCycle = 0; iCycle < iVisitsQty; iCycle++)
{
if (visit [iCycle]->fnGetDelete())
continue;
if (visit [iCycle]->fnGetServiceType() == -4) // виртуальная услуга
continue;
if (visit [iCycle]->fnGetPlannedTreatmentOnly())
continue;
if (config.bRegShowFutureVisits) // показывать в журнале будущие посещения
{
if (fnCompareDate(visit [iCycle]->fnGetDate(),szDate) == -1)
continue;
}
else
{
if (strcmp(visit [iCycle]->fnGetDate(),szDate))
continue;
}
// фильтрация по специалисту
if (iSpec != -1)
if (visit [iCycle]->fnGetSpec() != iSpec)
continue;
// если запрещено просматривать чужие записи
if (! config.bOtherPrefixView)
// проверяем авторство записей
if (strcmp(query->szPrefix,visit [iCycle]->fnGetPrefix()))
continue;
// заполняем структуру для сортировки
iClientNum = visit [iCycle]->fnGetClientNum();
iClient = visit [iCycle]->fnGetClientIndex();
if (iClient == -1)
continue;
// критерий сортировки журнала посещений
switch (iTuningSortReg)
{
case 1: // сортировка по времени
// формат "ГГГГММДД_время_фамилия имя отчество_услуга"
sprintf (curr_row.szSort,"%04u%02u%02u_%s_%s %s %s_%s",
fnGetYear(visit [iCycle]->fnGetDate()),
fnGetMonth(visit [iCycle]->fnGetDate()),
fnGetDay(visit [iCycle]->fnGetDate()),
visit [iCycle]->fnGetTime(),
client [iClient]->fnGetFamily(),
client [iClient]->fnGetName(),
client [iClient]->fnGetName2(),
fnGetServiceName(visit [iCycle]->fnGetServiceType()));
break;
case 2: // сортировка по фамилиям
// формат "ГГГГММДД_фамилия имя отчество_время_услуга"
sprintf (curr_row.szSort,"%04u%02u%02u_%s %s %s_%s_%s",
fnGetYear(visit [iCycle]->fnGetDate()),
fnGetMonth(visit [iCycle]->fnGetDate()),
fnGetDay(visit [iCycle]->fnGetDate()),
client [iClient]->fnGetFamily(),
client [iClient]->fnGetName(),
client [iClient]->fnGetName2(),
visit [iCycle]->fnGetTime(),
fnGetServiceName(visit [iCycle]->fnGetServiceType()));
break;
}
curr_row.iVisitNum = visit [iCycle]->fnGetNum();
curr_row.iVisit = iCycle;
// iClientNum сохраняется, чтобы отличать Анонимного
curr_row.iClientNum = iClientNum;
curr_row.iClient = iClient;
// дата
if (config.bRegShowFutureVisits)
{
sprintf(curr_row.sz1,"%02u/%02u",
fnGetDay(visit [iCycle]->fnGetDate()),
fnGetMonth(visit [iCycle]->fnGetDate()));
curr_row.iSpan1 = 1;
}
// время
strcpy(curr_row.sz2,visit [iCycle]->fnGetTime());
curr_row.iSpan2 = 1;
// ФИО клиента
sprintf(curr_row.sz3,"%s %s %s",
client [iClient]->fnGetFamily(),
client [iClient]->fnGetName(),
client [iClient]->fnGetName2());
curr_row.iSpan3 = 1;
// специалист
curr_row.i4 = visit [iCycle]->fnGetSpec();
curr_row.iSpan4 = 1;
rows->fnPush((char *) &curr_row,sizeof (curr_row));
}
|
3. При необходимости производится сортировка записей в массиве rows.
// **************
// * сортировка *
// **************
if (iTuningSortReg)
rows->fnSort(0);
|
4. Для всех объединяемых столбцов пишутся циклы сканирования
ячеек на предмет совпадения содержимого. Циклы обычно немного отличаются,
часть столбцов содержат строки, часть содержат числовые значения.
// ****************************************************
// * циклы сканирования ячеек таблицы для объединения *
// ****************************************************
iQty = rows->fnGetQty() -1;
// дата
for (iCycle = 0; iCycle < iQty; iCycle++)
{
rows->fnPop((char *) &curr_row,iCycle);
for (iCycleRow = iCycle +1; iCycleRow <= iQty; iCycleRow++)
{
rows->fnPop((char *) &next_row,iCycleRow);
if (! strcmp(curr_row.sz1,next_row.sz1))
{
curr_row.iSpan1++;
next_row.iSpan1 = 0;
rows->fnReplace((char *) &next_row,iCycleRow,sizeof(next_row));
}
else
break;
}
if (curr_row.iSpan1 > 1)
{
rows->fnReplace((char *) &curr_row,iCycle,sizeof(curr_row));
iCycle += curr_row.iSpan1 -1;
}
}
// время
for (iCycle = 0; iCycle < iQty; iCycle++)
{
rows->fnPop((char *) &curr_row,iCycle);
for (iCycleRow = iCycle +1; iCycleRow <= iQty; iCycleRow++)
{
rows->fnPop((char *) &next_row,iCycleRow);
if (! strcmp(curr_row.sz2,next_row.sz2))
{
curr_row.iSpan2++;
next_row.iSpan2 = 0;
rows->fnReplace((char *) &next_row,iCycleRow,sizeof(next_row));
}
else
break;
}
if (curr_row.iSpan2 > 1)
{
rows->fnReplace((char *) &curr_row,iCycle,sizeof(curr_row));
iCycle += curr_row.iSpan2 -1;
}
}
// ФИО клиента
for (iCycle = 0; iCycle < iQty; iCycle++)
{
rows->fnPop((char *) &curr_row,iCycle);
for (iCycleRow = iCycle +1; iCycleRow <= iQty; iCycleRow++)
{
rows->fnPop((char *) &next_row,iCycleRow);
if (! strcmp(curr_row.sz3,next_row.sz3))
{
curr_row.iSpan3++;
next_row.iSpan3 = 0;
rows->fnReplace((char *) &next_row,iCycleRow,sizeof(next_row));
}
else
break;
}
if (curr_row.iSpan3 > 1)
{
rows->fnReplace((char *) &curr_row,iCycle,sizeof(curr_row));
iCycle += curr_row.iSpan3 -1;
}
}
// специалист
for (iCycle = 0; iCycle < iQty; iCycle++)
{
rows->fnPop((char *) &curr_row,iCycle);
for (iCycleRow = iCycle +1; iCycleRow <= iQty; iCycleRow++)
{
rows->fnPop((char *) &next_row,iCycleRow);
if (curr_row.i4 == next_row.i4)
{
curr_row.iSpan4++;
next_row.iSpan4 = 0;
rows->fnReplace((char *) &next_row,iCycleRow,sizeof(next_row));
}
else
break;
}
if (curr_row.iSpan4 > 1)
{
rows->fnReplace((char *) &curr_row,iCycle,sizeof(curr_row));
iCycle += curr_row.iSpan4 -1;
}
}
|
5. Ниже пример формирования столбцов с объединёнными
по вертикали ячейками. Переменные iSpan1, iSpan2, iSpan3 и iSpan4 в каждой
структуре содержат количество объединяемых ячеек или 0, если ячейка в текущей
строке таблицы не описывается.
// *****************************************
// * формирование таблицы с тегами rowspan *
// *****************************************
iQty = rows->fnGetQty();
for (iCycle = 0; iCycle < iQty; iCycle++)
{
rows->fnPop((char *) &curr_row,iCycle);
s = "<tr>";
// ********
// * дата *
// ********
if (config.bRegShowFutureVisits)
{
if (curr_row.iSpan1)
{
s += "<td";
if (curr_row.iSpan1 > 1)
{
sprintf(szTmp," rowspan=%i",curr_row.iSpan1);
s += szTmp;
}
s += ">";
s += curr_row.sz1;
s += "</td>";
}
}
// *********
// * время *
// *********
if (curr_row.iSpan2)
{
s += "<td";
if (curr_row.iSpan2 > 1)
{
sprintf(szTmp," rowspan=%i",curr_row.iSpan2);
s += szTmp;
}
s += ">";
s += curr_row.sz2;
s += "</td>";
}
// **********
// * клиент *
// **********
if (curr_row.iSpan3)
{
s += "<td";
if (curr_row.iSpan3 > 1)
{
sprintf(szTmp," rowspan=%i",curr_row.iSpan3);
s += szTmp;
}
s += ">";
if (! curr_row.iClientNum)
{
// анонимный клиент
s += client [curr_row.iClient]->fnGetFamily();
}
else
{
// ФИО клиента
s += "<a href=\"/client_edit?dst=reg&client_num=";
sprintf(szTmp,"%i",client [curr_row.iClient]->fnGetNum());
s += szTmp;
s += "\">";
s += curr_row.sz3;
s += "</a>";
}
s += "</td>";
}
|
// **************
// * специалист *
// **************
if (config.bInputSpec)
{
if (curr_row.iSpan4)
{
s += "<td";
if (config.bRegJournalValignTop)
s += " valign=top";
if (curr_row.iSpan4 > 1)
{
sprintf(szTmp," rowspan=%i",curr_row.iSpan4);
s += szTmp;
}
s += ">";
s += fnGetSpecFIO(curr_row.i4);
s += "</td>";
}
}
|
Другие, не объединяемые ячейки отображаются как обычно,
на основе содержащихся в структуре уникальных номеров и индексов посещения
и клиента.
|