Вот и настал долгожданный момент практики. Кто не читал предыдущий урок, обязательно с ним ознакомьтесь. В нем говорится о теории 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. Они выводятся поверх всего контента, при этом скролл работает только внутри всплывающего окна.
 
 
 
 
 
