yii2 все о gridview Часть 2

  • 11 апреля 2018
  • 5217
0%

Помогло?

Вот и настал долгожданный момент практики. Кто не читал предыдущий урок, обязательно с ним ознакомьтесь. В нем говорится о теории gridView (ссылка: здесь). А теперь я бы хотел предложить вам реализовать следующий функционал (можно не читать, а сразу перейти к Миграции):


1 Вывод таблицы типа Stripped (полосатая): когда одна строка имеет фон, а следующая - нет
2 Один из фильтров показывает данные по выбранным чекбоксам
3 Один из фильтров имеет выпадающий список
4 Одна из колонок содержит отформатированную дату
5 Одна из колонок содержит изображение
6 Если данные пустые, то мы закрашиваем ячейку в красный цвет
7 Обрезать длинные тексты, если они превышают 10 знаков
8 В футере вывести пояснения к ячейкам
9 Разместить 2 пагинации: одну - сверху, а другую-снизу.
10 Сделать заголовок синим
11 Сделать текст футера серого цвета
12 Вывести ошибки с фильтра вверх в общий контейнер
13 Изменить текст количества отображаемых элементов на странице
14 Если запись удалена, в ячейке использовать крестик, если активная - галочку


Структура базы данных, Миграции

Как и обычно, начнем с миграции базы данных. Дадим название БД: gridview. Напишем поочередно 2 команды:

php yii migrate/create books_init

и

php yii migrate/create books_insert

Будет создано 2 файла в папке /migrations. Поменяем функции safeUp и safeDown поочередно в каждом файле. Наполнение вы найдете в моем репозитории: github. Для примера можете посмотреть, как мы выполняли данные действия в этом уроке: Простое приложение на Yii2 и Angular 2 Часть1

Для файла books_init

<?php
use yii\db\Migration;
/**
 * Class m180406_065815_books_init
 */
class m180406_065815_books_init extends Migration
{
    public function safeUp()
    {
        $tableOptions = null;
        if ($this->db->driverName === 'mysql') {
            $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB';
        }
        $this->createTable('{{%books}}', [
            'id' => $this->primaryKey(),
            'title' => $this->string()->notNull(),
            'src' => $this->string(),
            'author' => $this->string()->notNull(),
            'description' => $this->text(),
            'created_at' => $this->integer()->notNull(),
            'delete' => $this->smallInteger()->notNull()->defaultValue(0),
        ], $tableOptions);
    }
    public function safeDown()
    {
        $this->dropTable('{{%books}}');
    }
}

Для файла books_insert:

<?php
use yii\db\Migration;
/**
 * Class m180406_065902_books_insert
 */
class m180406_065902_books_insert extends Migration
{
    public function safeUp()
    {
        $insert = [
            [
                'title' => 'Ленинград. Невероятная и правдивая история группы',
                'src' => 'http://ozon-st.cdn.ngenix.net/multimedia/c300/1019082045.jpg',
                'author' => 'Семеляк Максим Анатольевич',
                'description' => 'Книга, которую ждут миллионы - АВТОРИЗОВАННАЯ БИОГРАФИЯ ГРУППЫ ЛЕНИНГРАД! В этом году возрожденному "Ленинграду" исполняется 20 лет. Десятки альбомов, сотни видеоклипов, миллионы просмотров и скачиваний. "Ленинград" создает хиты и мемы. Творчество Сергея Шнурова обсуждают и осуждают, хвалят и ругают. Его любят и ненавидят, но равнодушных к песням Шнура просто нет. В книге "Ленинград. Невероятная и правдивая история" писатель и журналист, друг Сергея Шнурова, Максим Семеляк попытался объяснить феномен Шнура. И честно рассказать о том, как самая популярная группа страны работала все эти годы. В книгу включены размышления Сергея Шнурова, его интервью, заметки и комментарии. А также очень много фотографий из личных альбомов, многие из которых ранее нигде не публиковались.',
                'created_at' => '1371201284',
                'delete' => 0,
            ],
            [
                'title' => 'Цветы для Элджернона',
                'src' => 'http://ozon-st.cdn.ngenix.net/multimedia/c300/1012950149.jpg',
                'author' => 'Дэниел Киз',
                'description' => '"Цветы для Элджернона" Дэниела Киза входят в программу обязательного чтения в американских школах. Это единственная история в жанре научной фантастики, автор которой был дважды награжден сначала за рассказ, а потом за роман с одним и тем же названием, героем, сюжетом. ',
                'created_at' => '1427966084',
                'delete' => 0,
            ],
            [
                'title' => 'В Питере жить. От Дворцовой до Садовой, от Гангутской до Шпалерной. Личные истории',
                'src' => 'http://ozon-st.cdn.ngenix.net/multimedia/c300/1018537441.jpg',
                'author' => 'Елена Данииловна Шубина',
                'description' => '"В Питере жить" - это вам не в Москве, о которой нам рассказали в книге-бестселлере "Москва: место встречи". Что и говорить - другая ментальность, петербургский текст. Евгений Водолазкин, Андрей Аствацатуров, Борис Гребенщиков, Елизавета Боярская, Андрей Битов, Михаил Пиотровский, Елена Колина, Михаил Шемякин, Татьяна Москвина, Валерий Попов, "митёк" Виктор Тихомиров, Александр Городницкий и многие другие "знаковые лица" города на Неве - о питерских маршрутах и маршрутках, дворах-колодцах и дворцах Растрелли, Васильевском острове, Московском проспекте и платформе Ржевка, исчезнувшем в небытии Введенском канале и «желтом паре петербургской зимы"…
	        Книга иллюстрирована акварелями Лизы Штормит и рисунками Виктора Тихомирова, на переплете - офорт Михаила Шемякина. ',
                'created_at' => '1458304450',
                'delete' => 0,
            ],
            [
                'title' => 'Семь навыков высокоэффективных людей.',
                'src' => 'http://ozon-st.cdn.ngenix.net/multimedia/c300/1018960911.jpg',
                'author' => 'Стивен Р. Кови',
                'description' => 'Во-первых, эта книга излагает системный подход к определению жизненных целей, приоритетов человека. Эти цели у всех разные, но книга помогает понять себя и четко сформулировать жизненные цели. Во-вторых, книга показывает, как достигать этих целей. И в-третьих, книга показывает, как каждый человек может стать лучше. Причем речь идет не об изменении имиджа, а о настоящих изменениях, самосовершенствовании. Книга не дает простых решений и не обещает мгновенных чудес. Любые позитивные изменения требуют времени, работы и упорства. Но для людей, стремящихся максимально реализовать потенциал, заложенный в них природой, эта книга - дорожная карта.',
                'created_at' => '1467462131',
                'delete' => 1,
            ],
            [
                'title' => 'Приключения Эмиля из Леннеберги',
                'src' => 'http://ozon-st.cdn.ngenix.net/multimedia/c300/1007505349.jpg',
                'author' => 'Emil i Lonneberga',
                'description' => 'Веселая повесть про Эмиля из Леннеберги, которую написала замечательная шведская писательница Астрид Линдгрен, а на русский язык блистательно пересказала Лилианна Лунгина, полюбилась и взрослым и детям всей планеты. Этот вихрастый мальчуган - ужасный озорник, он и дня не проживет, не напроказничав. Ну кому придет в голову гонять кошку, чтобы проверить, хорошо ли она прыгает?! Или надеть на себя супницу? Или поджечь перо на шляпе у пасторши? Или поймать в крысоловку родного отца, а поросенка накормить пьяными вишнями?..',
                'created_at' => '1460203863',
                'delete' => 0,
            ],
            [
                'title' => 'English Grammar in Use with Answers',
                'src' => Null,
                'author' => 'Рэймонд Мерфи',
                'description' => 'English Grammar in Use Fourth Edition is an updated version of the best-selling grammar title.
	        This new edition with answers:
	        has a fresh, appealing new design and clear layout, with revised and updated examples;
	        is arranged in a tried-and-trusted, easy to use format, with explanations of grammar points on each left-hand page and exercises to check understanding on the right;
	        is perfect for independent studying and the study guide helps learners to identify which language points to focus on;
	        contains lots of additional practice exercises to consolidate learning.',
                'created_at' => '1496315482',
                'delete' => 0,
            ]
        ];
        $this->batchInsert('{{%books}}', [
            'title',
            'src',
            'author',
            'description',
            'created_at',
            'delete',
        ], $insert);
    }
    public function safeDown()
    {
        $this->truncateTable('{{%books}}');
    }
}

После наполнения выполните команду:

php yii migrate

Теперь наша база данных готова!

Модели и контроллеры

Далее нам нужно создать базовую модель (в нашем случае это Books) и ту, которая будет работать с ActiveDataProvider, чтобы организовать фильтрацию, сортировку и пагинацию. Приступим к созданию главной модели: Books. Расположить ее нужно в папке /models. Листинг этого кода вы найдете на github

Также в этой папке рядом нужно создать файл: BooksSearch.php. Здесь я прокомментирую листинг кода

<?php

namespace app\models;

use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use app\models\Books;

/**
 * BooksSearch represents the model behind the search form of `app\models\Books`.
 */
class BooksSearch extends Books
{
    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['id', 'created_at', 'delete'], 'integer'],
            [['title', 'src', 'author', 'description'], 'safe'],
        ];
    }

    /**
     * @inheritdoc
     */
    public function scenarios()
    {
        // bypass scenarios() implementation in the parent class
        return Model::scenarios();
    }

    /**
     * Creates data provider instance with search query applied
     *
     * @param array $params
     *
     * @return ActiveDataProvider
     */
    public function search($params)
    {
        $query = Books::find();

        // add conditions that should always apply here

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);

        $this->load($params);

        if (!$this->validate()) {
            // uncomment the following line if you do not want to return any records when validation fails
            // $query->where('0=1');
            return $dataProvider;
        }

        // grid filtering conditions
        $query->andFilterWhere([
            'id' => $this->id,
            'created_at' => $this->created_at,
            'delete' => $this->delete,
        ]);

        $query->andFilterWhere(['like', 'title', $this->title])
            ->andFilterWhere(['like', 'src', $this->src])
            ->andFilterWhere(['IN', 'author', $this->author])
            ->andFilterWhere(['like', 'description', $this->description]);

        return $dataProvider;
    }
}

Наш класс BooksSearch наследуется от класса Books. Это нужно для того, чтобы получить доступ к важному классу Model, на котором и держится наша валидация, а также, при возможности, дополнительные функции и свойства родителя. Например, различные связи (relations)

Ниже идет полностью переопределенный метод rules. Так как нам не нужно здесь правило required, нам важно, чтобы значения в принципе отправлялись (это касается фильтра) и имели нужный нам тип (например, чтобы защититься от SQL инъекции) Замечу, что благодаря свойствам этой модели и строится фильтр. Если свойства в этой модели не будет, то и поле в фильтре вы не увидите. Говоря, простым языком: все, что есть в function rules(), будет работать в фильтрации. И логично предположить: все, что отправляется из фильтра, попадает сюда. Сценарии мы просто пропустим, там ничего интересного, и сразу переходим к методу search()

В методе search() работает основная логика отображения данных. Во-первых, обратите внимание на переменную $query - это instance запроса ActiveRecord (то есть, select, where, join), НО! Здесь не должны использоваться методы ->all(), ->one(), ->count() Это очень важно! За вас всё сделает ActiveDataProvider. Там и будут работать count при построении пагинации и offset() и и выборка данных на основе условий и прочее.

Вот мы и дошли до самой основной переменной $dataProvider. Это результат класса ActiveDataProvider(). Если вам интересно, вы можете посмотреть, что там внутри. В двух словах: там настройки пагинации, сортировки и много всего остального. Можете вкратце ознакомиться с гайдом (тут все нормально написано: ссылка)
В целом, вы его можете подкрутить в плане: в каком порядке выводить данные при инициализации и по сколько записей на странице.

Теперь обратите внимание на конструкцию:

$this->load($params);

Она означает, что параметры, пришедшие с фильтра, "вгрузились" в модель, и с ее свойствами можно будет работать (если они валидны конечно).

Далее идет конструкция:

if (!$this->validate()) {

Что в буквальном смысле означает: если не валидно, то просто проинициализировать $dataProvider (по умолчанию)

Но если модель валидная и реально пришли параметры, то мы можем повлиять на переменную $query. Так вот $query->andFilterWhere означает: если в модель удалось вгрузить это свойство, то оно участвует в выдаче, если нет, то ничего не происходит, и данный метод не отработает. Давайте разберем на примере: пусть пришел параметр "delete", то есть пользователь выбрал в фильтре что-то связанное с удалением. Это значит, что будет построен SQL запрос: SELECT * FROM gridview WHERE delete = 1. Если же этого параметра не пришло, то ничего в "where" не попадет.

Теперь осталось создать контроллер, заходите в /controllers/SiteController.php найдите там actionIndex (63 строчка), добавьте следующее:

$searchModel = new BooksSearch(); 
$dataProvider = $searchModel->search(Yii::$app->request->queryParams); 
return $this->render('index', [ 'searchModel' => $searchModel, 'dataProvider' => $dataProvider, ]);

Я здесь заострю внимание на Yii::$app->request->queryParams. Образно говоря: взять все параметры и попытаться вгрузить их в метод search(). Там он уже будет разбираться и валидировать все. Теперь переходим к самому пикантному файлу: /views/site/index.php. Там находится основной код построение gridView виджета

View

<?php

use yii\helpers\Html;
use yii\grid\GridView;

/* @var $this yii\web\View */
/* @var $searchModel app\models\BooksSearch */
/* @var $dataProvider yii\data\ActiveDataProvider */

$this->title = 'Books';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="books-index">

    <h1><?= Html::encode($this->title) ?></h1>
    <?php // echo $this->render('_search', ['model' => $searchModel]); ?>

    <p>
        <?= Html::a('Create Books', ['create'], ['class' => 'btn btn-success']) ?>
    </p>

    <?= GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'tableOptions' => ['class' => 'table'],
        'showFooter' => true,
        'layout' => "{errors}\n{pager}\n{items}\n{pager}\n{summary}",
        'summary' => '<i>Сейчас вы видите от </i> <b>{begin}</b> до <b>{end}</b> записей (всего: <b style="color: #090">{totalCount}</b>)',
        'headerRowOptions' => [
            'class' => 'headerCustomRow'
        ],
        'footerRowOptions' => [
            'style' => 'color: #888;font-style: italic;'
        ],
        'rowOptions' => function($model, $key, $index){
            return [
                'class' => ($index % 2 == 0) ? 'stripShow' : ''
            ];
        },
        'columns' => [
            ['class' => 'yii\grid\SerialColumn'],

            'id',
            [
                'attribute' => 'title',
                'contentOptions' => function($model){
                    return [
                        'class' => (empty($model->title)) ? 'empty-cell' : ''
                    ];
                },
                'footer' => 'Здесь рассказывается о заголовоке книги'
            ],
            [
                'attribute' => 'src',
                'format' => 'image',
                'filter' => false,
                'contentOptions' => function($model){
                    return [
                        'class' => (empty($model->src)) ? 'empty-cell' : ''
                    ];
                },
                'footer' => 'Куда же без картинки?'
            ],
            [
                'attribute' => 'author',
                'filter' => Html::activeCheckboxList($searchModel, 'author',
                    \yii\helpers\ArrayHelper::map(\app\models\Books::find()->all(), 'author', 'author')),
                'contentOptions' => function($model){
                    return [
                        'class' => (empty($model->author)) ? 'empty-cell' : ''
                    ];
                },
                'footer' => 'Самые любимые и уважаемые всеми, авторы данных книг'
            ],
            [
                'attribute' => 'description',
                'content' => function($model){
                    return \yii\helpers\StringHelper::truncateWords($model->description, 10);
                },
                'footer' => 'Полное описание, к каждому произведению, чтобы читатель сразу мог понять о чем книга'
            ],

            [
                'attribute' => 'created_at',
                'format' => ['date', 'php:d/m/Y'],
                'contentOptions' => function($model){
                    return [
                        'class' => (empty($model->created_at)) ? 'empty-cell' : ''
                    ];
                }
            ],
            //'delete',
            [
                'attribute' => 'delete',
                'format' => 'raw',
                'filter' => [
                    'Активные',
                    'В корзине'
                ],
                'contentOptions' => [
                    'width' => '150px'
                ],
                'content' => function($model){
                    return ($model->delete == 0) ? '<span style="color:#090" class="glyphicon glyphicon-ok"></span>' :
                        '<span style="color:#c11" class="glyphicon glyphicon-remove"></span>';
                }
            ],
        ],
    ]); ?>
</div>

<style>
    .empty-cell{
        background: #c12e2a;
    }
    .headerCustomRow, .headerCustomRow th, .headerCustomRow a{
        background: #337ab7;
        color: #fff;
    }
    .stripShow{
        background: #e9f1ff
    }
</style>

 

Теперь давайте на примере кода ответим на все вопросы, которые мы задали себе вначале:

1 Вывод таблицы типа Stripped (полосатая): когда одна строка имеет фон, а следующая - нет

Понятное дело, мы могли это сделать на CSS. Более того это уже сделано по умолчанию, но мы тренируемся в работе функций виджета, поэтому для начала я отключил общий класс таблицы, сделал обычный ее вариант:

'tableOptions' => ['class' => 'table'], 

И сама функция, которая нам нужна это rowOptions

'rowOptions' => function($model, $key, $index){ 
	return [
		'class' => ($index % 2 == 0) ? 'stripShow' : '' 
	]; 
},

Мы берем каждый индексный номер и пытаемся нацело поделить на 2. Если делится без остатка, то добавляем класс к stripShow. Ниже я описал это свойство в теге <style>

2 Один из фильтров показывает данные по выбранным чекбоксам

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

'filter' => Html::activeCheckboxList($searchModel, 'author',
                    \yii\helpers\ArrayHelper::map(\app\models\Books::find()->all(), 'author', 'author')),

ActiveCheckboxList принимаем модель, название атрибута, а также массив, данный для построения чекбокса. В нашем случае это массив вида:

[ 'Дэниел Киз' => 'Дэниел Киз' ]

где ключ используется для передачи в модель, а значение - для вывода в чекбокс лист. Также обратите внимание на ArrayHelper. Функция map выберет только те значения, которые нам нужны из выборки

3 Один из фильтров имеет выпадающий список

Здесь вообще все просто: также переопределяем фильтр, только задаем обычный массив:

'filter' => [ 'Активные', 'В корзине' ],

Еще я добавил свойство, которое фиксирует ширину ячейки, чтобы текст внутри select'a хорошо читался:

'contentOptions' => [ 'width' => '150px' ],

4 Одна из колонок содержит отформатированную дату

Используем встроенный форматер, а для представления формат php дат:

'attribute' => 'created_at', 
'format' => ['date', 'php:d/m/Y'],

5 Одна из колонок содержит изображение

Повторяем предыдущие действия: используем форматтер, а фильтр вообще отключим:

'attribute' => 'src', 
'format' => 'image', 
'filter' => false,

6 Если данные пустые, то мы закрашиваем ячейку в красный цвет

Решение, которое я вам предложу мне не очень нравится. Оно получается несколько громоздким, но, тем не менее, иных вариантов я не нашел. Если есть идеи, пишите ваши предложения. Мое решение - добавить к каждой ячейке данное свойство:

'contentOptions' => function($model){
    return [
        'class' => (empty($model->src)) ? 'empty-cell' : ''
    ];
},

Соответветственно, ниже вы найдете опеределение этого CSS класса

7 Обрезать длинные тексты, если они превышают 10 знаков

Для этих целей отлично подойдет встроенный StringHelper, а свойство, которое нам нужно - это контент, отвечающий за вывод данных в ячейку:

'content' => function($model){
                    return \yii\helpers\StringHelper::truncateWords($model->description, 10);
                },

8 В футере вывести пояснения к ячейкам

Для начала нам нужно активировать сам футер:

'showFooter' => true,

А теперь в каждой ячейке можно написать пояснение:

'footer' => 'Здесь рассказывается о заголовоке книги'

9 Разместить 2 пагинации, одну сверху другую снизу И 12 Вывести ошибки с фильтра вверх в общий контейнер

Я объединил два вопроса в один, так как они решаются на уровне layout:

'layout' => "{errors}\n{pager}\n{items}\n{pager}\n{summary}",

ошибки - это {errors} - теперь можно проверить попробуйте в фильтер created_at передать строку.

{pager} - это пагинации. Для того, чтобы ее увидеть, я бы предложил зайти в BooksSearch и найти там наш ActiveDataProvider, добавить лимит на отображаемые записи, например, 5. Должно получиться как-то так:

$dataProvider = new ActiveDataProvider([
    'query' => $query,
    'Pagination' => [
        'pageSize' => 5
    ]
]);

10 Сделать заголовок синим и 11 Сделать текст футера серого цвета

Я также объединил 2 этих вопроса. Для заголовка это свойство headerRowOptions и для футера footerRowOptions (но не забывайте, что свойство showFooter должно быть true). Получилось вот так:

'headerRowOptions' => [ 'class' => 'headerCustomRow' ], 'footerRowOptions' => [ 'style' => 'color: #888;font-style: italic;' ],

CSS свойство headerCustomRow задано ниже

13 Изменить текст количества отображаемых элементов на странице

'summary' => 'Сейчас вы видите от  {begin} до {end} записей (всего: {totalCount})',

Здесь, я думаю, все понятно, и отсутствует необходимость в комментариях.

14 Если запись удалена в ячейке использовать - крестик, если активная то - галочку

Также пользуемся свойством content в внутри ячейки:

'content' => function($model){
                    return ($model->delete == 0) ? '' :
                        '';
                }

В моем случае выводится glyphicon, и, соответственно, условие: если delete == 0, то мы выводим галочку, если 1 - крестик.

Заключение

На этом все. Если вы нашли какие-то неточности или даже ошибки, то обязательно пишите о них мне, а я буду исправлять и делать для вас контент лучше. Лучи добра!

Содержание

Облако тегов

Следующая статья

Bootstrap 4.1 – модальное окно

Добро пожаловать! При построении модальных окошек используются HTML, JS, и CSS. Они выводятся поверх всего контента, при этом скролл работает только внутри всплывающего окна.

Сообщения: (4)

?
Публикуя сообщение, вы соглашаетесь с пользовательским соглашением
ВВсеволод Карнаухов1 год назад
Привет Автор, можешь показать результат своей работы (картинку таблицы которая у тебя получилась)?
Ответить
?
Публикуя сообщение, вы соглашаетесь с пользовательским соглашением
Скрыть ответы: (3)
ААдминистратор1 год назад
Привет, Всеволод! Спасибо за обратную связь. Правда, забыл пример разместить. На днях разверну проект с гитхаба (ссылка в статье фигурирует) и сделаю апдейт статьи для вас. В планах еще записать видео... Но сейчас совсем нет времени на все это :(
Ответить
ААндрей1 год назад
@Администратор, согласен, ну хотябы внешний вид таблицы. Спасибо
Ответить
ААдминистратор1 год назад
@Андрей, Спасибо за вашу обратную связь! Нашел таки время и "запилил" полноценную демку. Теперь даже можно потыкать и посмотреть как оно работает.

З.Ы Не претендую на красоту исполнения. Урок, чтобы просто показать возможности GridView ;)
Ответить

Читайте также