CGridView и "сложная" шапка

Идея была такова - отобразить шапку в несколько строк с объединением столбцов и строк (использованием rowspan и colspan). В итоге должно получиться следующее (ссылка на скачивание файла внизу страницы):

Итак, приступим.
Переопределяем класс CGridView и его методы: initColumns() и renderTableHeader().

protected function initColumns() {
        if ($this->columns === array()) {
            if ($this->dataProvider instanceof CActiveDataProvider) {
                $this->columns = $this->dataProvider->model->attributeNames();
            } else if ($this->dataProvider instanceof IDataProvider) {
                // use the keys of the first row of data as the default columns
                $data = $this->dataProvider->getData();
                if (isset($data[0]) && is_array($data[0]))
                    $this->columns = array_keys($data[0]);
            }
        }

        /* Получаем массив ключей колонок */
        $columns = array();
        foreach ($this->columns as $k => $v) {
            $columns[is_array($v) ? $v['name'] : $v] = $v;
        }

        /* Формируем шапку таблицы */
        $this->renderHeaderRow($this->headers, $columns);

        /* Исключаем колонки, не присутствующие в шапке */
        $this->columns = array();
        foreach ($this->headerColumns as $v) {
            if (is_array($columns[$v])) {
                $this->columns[] = $columns[$v];
            } else {
                $this->columns[] = $columns[$v];
            }
        }

        $this->headerColumns = array();
        $id = $this->getId();
        foreach ($this->columns as $i => $column) {
            if (is_string($column))
                $column = $this->createDataColumn($column);
            else {
                if (!isset($column['class']))
                    $column['class'] = 'CDataColumn';
                $column = Yii::createComponent($column, $this);
            }
            if (!$column->visible) {
                unset($this->columns[$i]);
                continue;
            }
            if ($column->id === null)
                $column->id = $id . '_c' . $i;
            $this->columns[$i] = $column;
            $this->headerColumns[$column->name] = $i;
        }

        foreach ($this->columns as $column) {
            $column->init();
        }
    }

Функция формирования строк шапки таблицы:

    private function renderHeaderRow($row, $columns, $level = 0) {
        $colspan = 0;
        foreach ($row as $k => $item) {
            if (is_array($item)) {
                if (isset($item['childs'])) {
                    if (count($this->headerRows)) {

                    }
                    $count = $this->renderHeaderRow($item['childs'], $columns, $level + 1);
                    if ($count) {
                        $this->headerRows[$level][] = array(
                            'colspan' => $count,
                            'header' => isset($item['label']) ? $item['label'] : $k
                        );
                        $colspan += $count - 1;
                    } else {
                        unset($row[$k]);
                    }
                }
            } else {
                if (array_key_exists($item, $columns)) {
                    $this->headerRows[$level][] = array(
                        'name' => $item
                    );
                    $this->headerColumns[] = $item;
                } else {
                    unset($row[$k]);
                }
            }
        }
        return count($row) + $colspan;
    }

И, наконец, вывод шапки: необходимо заменить строки в renderTableHeader()

  echo "\n";
  foreach($this->columns as $column)
   $column->renderHeaderCell();
  echo "\n";
на
    for ($r = 0; $r < count($this->headerRows); $r++) {
                $row = $this->headerRows[$r];
                echo '';
                foreach ($row as $k => $v) {
                    $htmlOptions = array();
                    $colspan = (isset($v['colspan'])) ? $v['colspan'] : false;
                    if ($colspan > 1) {
                        $htmlOptions['colspan'] = $colspan;
                    }
                    $rowspan = ($r < count($this->headerRows) && !$colspan) ? count($this->headerRows) - $r : false;
                    if ($rowspan) {
                        $htmlOptions['rowspan'] = $rowspan;
                    }

                    if (isset($v['name'])) {
                        $column = $this->columns [$this->headerColumns[$v['name']]];
                        $column->headerHtmlOptions = $htmlOptions;
                        $column->renderHeaderCell();
                    } else {
                        echo $this->renderHeaderCellSimple(isset($v['header']) ? $v['header'] : " ", $htmlOptions);
                    }
                }
                echo '';
            }


Вызов этого компонента осуществляется следующим образом:
$this->widget('MTable', array(
 'id'=>'obj',
 'dataProvider'=>$model->search(),
 'filter'=>$model,
 'columns'=>array(
                'id',
                'title',
                'address',
                'kray_id',
                'city_id' => array(
                    'name'=>'city_id',
                    'value'=>'$data->city->title', //вот это важно
                    'filter'=>CHtml::listData(City::model()->findAll(), 'id', 'title'),                    
                ),
            ),
 'headers'=>array(
                'id',
                'title',
                'data' => array(
                    'label'=>Yii::t('main', 'Данные'), // если label отсутствует, то будет выведен ключ элемента (в данном случае 'data')
                    'childs'=>array(
                        'address',
                        'geo' => array(
                            'label'=>Yii::t('main', 'Местоположение'),
                            'childs'=> array(
                                'city_id',
                                'kray_id',
                            ),
                        ),
                    ),
                ),
            ),
)); 

Массивы $columns и $headers могут различаться по составу, но в итоге будут выведены только поля, присутствующие в обоих массивах.

Скачать

Комментарии

  1. спасибо большое, нужная статья.
    а подскажите, пожалуйста, как новый класс с таблицей к проекту привязать?

    ОтветитьУдалить
    Ответы
    1. Необходимо поместить файл в папку components. Пример вызова приведен в конце статьи ($this->widget('MTable'....). Будут еще вопросы - спрашивайте )

      Удалить
  2. Добрый день. Вопрос вот в чём, будет ли это всё работать с колонками типа CButtonColumn, то есть с теми, у которых отсутствует аттрибут name.. Такое впечатление, что нет..
    Есть предложение писать так :

    protected function initColumns(){
    .......................
    $id = null;
    if(isset($column->name))
    $id = $column->name;
    elseif(isset($column->id)){
    $id = $column->id;
    }
    if($id)
    $this->headerColumns[$id] = $i;
    }

    ОтветитьУдалить
    Ответы
    1. c CButtonColumn без отдельной обработки работать не будет - это факт :)
      Просто в моем проекте эта колонка в принципе отсутствует и не нужна, т.к. по клику на строку открывается диалоговое окно, где и производятся все манипуляции.

      Удалить
  3. И ещё, в классе renderTableHeader нужно ведь в цикле выводить tr - ки, а не просто пустые строки

    ОтветитьУдалить
    Ответы
    1. обновила пост :) И залила новый файл. Если интересно - посмотрите.
      В целом это даже не конечная версия, но наиболее близкая к той, что была опубликована здесь.
      Добавлена возможность задания id строкам таблицы, и исправлены ошибки, которые были раньше.

      Удалить
  4. К сожалению, файл недоступен для скачивания.

    ОтветитьУдалить
    Ответы
    1. в очередной раз загрузила новый файл :) Теперь на гуглдоках, надеюсь, больше он не пропадет :)

      Удалить
    2. Большущее спасибо, вы спасли мои мозги))

      Удалить
  5. Спасибо за пост. Всё работает как надо. Жаль только, что поля без атрибута 'name' не обрабатываются

    ОтветитьУдалить
    Ответы
    1. Не думаю, что это проблема, если очень нужно, то и такую задачу можно решить :)

      Удалить
  6. Классное расширение. Вот добавить бы еще объединение строк и группировку, но не так заморочено как здесь:

    http://groupgridview.demopage.ru/index.php?r=site/extrarowmerge

    и цены бы не было )

    ОтветитьУдалить
    Ответы
    1. Для объединения строк и группировки уже вроде есть готовые решения :) Правда чаще они платные )

      Удалить
    2. Да есть, но они без "сложной шапки". А что есть даже платные решения?) Не встречал, кроме как комплексных решений, ладно.

      Вот если бы вы доделали ваше расширение, было бы очень очень здорово.

      Удалить
    3. Можешь доделать ты, т.к. сейчас я мало работаю в Yii :) Но буду только рада дополнить этот пост :)

      Удалить
    4. Хотелось бы, но у меня сейчас катастрофически не хватает времени (

      но я бы мог попробовать проспонсировать новые доработки =)
      Как насчёт попробовать опять поработать в Yii ?))

      Удалить
  7. Девушка пишет такие штуки - молодцом!

    ОтветитьУдалить
  8. В protected function initColumns:

    /* Исключаем колонки, не присутствующие в шапке */
    $this->columns = array();
    foreach ($this->headerColumns as $v) {
    if (is_array($columns[$v])) {
    $this->columns[] = $columns[$v];
    } else {
    $this->columns[] = $columns[$v];
    }
    }

    в if видимо что-то не так:))

    ОтветитьУдалить
  9. Хорошая вещь, пришлась очень кстати, благодарю.

    ОтветитьУдалить
  10. можете обеснить
    'city_id' => array(
    'name'=>'city_id',
    'value'=>'$data->city->title', //вот это важно
    'filter'=>CHtml::listData(City::model()->findAll(), 'id', 'title'),

    ОтветитьУдалить
    Ответы
    1. Что именно объяснить?
      Для поля city_id мы описываем, откуда должны браться данные и в каком виде выводиться.
      name - поле таблицы, которая является моделью для CGridView
      value - значение, которое должно быть выведено. В данном случае мы используем поле title связанной модели, которая описана через связь city
      filter - этот параметр задает, что будет выведено в строке фильтра. В данном случае выпадающий список, в качестве значений которого выводятся названия городов из модели City

      Удалить
    2. Этот комментарий был удален автором.

      Удалить
    3. Этот комментарий был удален автором.

      Удалить
  11. все понял.

    у вас тут есть маленький ошибка в начале файла
    наследование от Grida

    ОтветитьУдалить
  12. можешь подсказать как сделать в шапке кнопку и выпадающий список

    ОтветитьУдалить
    Ответы
    1. вот здесь я описывала, как это делается http://progergirl.blogspot.ru/2012/06/cgridview.html

      Удалить
  13. Этот комментарий был удален автором.

    ОтветитьУдалить
  14. Подскажи плз как сделать такую шапку. http://prntscr.com/6v9zlb

    при How_many нужно выводить связзаные данные в отдельные столбцы (столбец = дата) все обыскал ничего подобного не нашел.

    Спасибо.

    ОтветитьУдалить
    Ответы
    1. Очевидно, тебе нужно формировать из полученных данных массив для вывода и использовать CArrayDataProvider для грида. Другого варианта я не вижу, т.к. грид сам по себе не сможет транспонировать данные перед выводом в том виде, в каком ты хочешь

      Удалить
  15. все можно сделать в три шага. смотреть тут http://des1roer.blogspot.ru/2015/05/yii_25.html

    ОтветитьУдалить

Отправить комментарий

Популярные сообщения