Yii2 ListView: Create Custom LinkPager Class - Part2

 ·  · 

This is the part2 of the tutorial. In this part we will register JavaScript to make the custom pager widget work.

Take a glance again at the screenshot about what effect we will create.

yii2_custom_linkpager_class_widget

We've finished rendering work of the new custom DemoPager widget in part1. Next we'll add JavaScript support in this part.

 

8 Register Widget Assets

Generally, the required JavaScript helper files or CSS files by a Yii2 widget should be packaged with widget together. This can be achieved by using yii\web\AssetBundle.

8.1 Register JS In DemoPagerAsset

Register JavaScript file in DemoPagerAsset.php.

<?php
// DemoPagerAsset.php
namespace frontend\widgets\demopager;

use yii\web\AssetBundle;

class DemoPagerAsset extends AssetBundle
{
    public $js = [
        'js/demopager.js'
    ];

    public $css = [
        /* You can add extra CSS file here if you need */
        // 'css/demopager.css'
    ];

    public $depends = [
        // we will use jQuery
        'yii\web\JqueryAsset'
    ];

    public function init()
    {   
        // Base path of current widget
        $this->sourcePath = __DIR__ . "/assets";
        parent::init();
    }
}
?>

8.2 Register DemoPagerAsset In DemoPager Widget

To use the JavaScript and CSS files registered in DemoPagerAsset, we still need register DemoPagerAsset itself to DemoPager widget. We do this step in run method of DemoPager widget.

// DemoPager.php
namespace frontend\widgets\demopager;

class DemoPager extends \yii\widgets\LinkPager
{
    // ...

    public function run()
    {
        // Register our widget assets
        DemoPagerAsset::register($this->getView());

        return preg_replace_callback("/{(\\w+)}/", function ($matches) {
            $sub_section_name = $matches[1];
            $sub_section_content = $this->renderSection($sub_section_name);

            return $sub_section_content === false ? $matches[1] : $sub_section_content;
        }, $this->pager_layout);
    }

    // ...
}

After registering, Yii2 will echo script tags automatically before HTML end body tag.

custom_yii2_linkpager_register_assets

 

9 demopager.js

Implement our demopager.js.

  • when user select a new page size, we'll replace page size parameter in query strings of current URL with new selected size and then refresh browser;
  • when user input a new target page number and press enter key, we'll replace page parameter in query strings of current URL with new page number, then jump to the new URL;
/* demopager.js */

var demoPagerWidget = {
    init: function (options) {
        if (options.pageSizeParam == "undefined" || !(options.pageSizeParam)) {
            return;
        }

        if (options.pageParam == "undefined" || !(options.pageParam)) {
            return;
        }

        if (options.url == "undefined" || !(options.url)) {
            return;
        }

        this.bindEvent(options.pageSizeParam, options.pageParam, options.url);
    },
    bindEvent: function (pageSizeParam, pageParam, url) {
        // $('input[name="page"]')
        $('input[name="' + pageParam + '"]').on('keydown', function (evt) {
            if (evt.which == 13) {
                var targetPage =  $(this).val();

                // new RegExp("\(&page=\)\\d+", "gi")
                var pattern = new RegExp("\(&" + pageParam + "=\)\\d+", "gi");
                var newUrl = url.replace(pattern, "$1"+targetPage);
                window.location.href = newUrl;
            }
        });

        // $('select[name="per-page"]')
        $('select[name="' + pageSizeParam + '"]').on('change', function () {
            var selectedPageSize = $(this).find('option:selected').val();

            // new RegExp("\(&per-page=\)\\d+", "gi")
            var pattern = new RegExp("\(&" + pageSizeParam + "=\)\\d+", "gi");
            var newUrl = url.replace(pattern, "$1"+selectedPageSize);
            window.location.href = newUrl;
        });
    }
};

Next, we need call above init function and pass pageSizeParam, pageParam, url parameters from PHP to JavaScript. We use registerJs method of yii\web\View to do the magic.

<?php
// DemoPager.php
namespace frontend\widgets\demopager;

use Yii;
use yii\helpers\Html;
use yii\helpers\Json;

class DemoPager extends \yii\widgets\LinkPager
{
    // ...

    public function run()
    {
        if ($this->registerLinkTags) {
            $this->registerLinkTags();
        }

        // Register our widget assets
        DemoPagerAsset::register($this->getView());

        // Params will be passed to javascript
        $jsOptions = [
            'pageParam' => $this->_page_param,
            'pageSizeParam' => $this->_page_size_param,

            // Current url
            'url' => $this->pagination->createUrl($this->pagination->getPage())
        ];

        // Register inline javascript codes
        // call init method, pass params
        $this->getView()->registerJs("demoPagerWidget.init(" . Json::encode($jsOptions) . ");");

        return preg_replace_callback("/{(\\w+)}/", function ($matches) {
            $sub_section_name = $matches[1];
            $sub_section_content = $this->renderSection($sub_section_name);

            return $sub_section_content === false ? $matches[1] : $sub_section_content;
        }, $this->pager_layout);
    }

    // ...
}

registerJs method will output inline JavaScript code blocks, which looks like in picture below.

custom_yii2_linkpager_registerJS

 

10 Usage

To allow flexible page size, you should not hard coded the page size in your controller.

<?php
// YOUR_APP/controllers/TestController.php
namespace frontend\controllers;

use Yii;
use yii\web\Controller;
use yii\data\ArrayDataProvider;

class TestController extends Controller
{
    public function actionTest()
    {
        // Get page size from query strings
        $page_size = Yii::$app->request->get('per-page');
        $page_size = isset( $page_size ) ? intval($page_size) : 4;


        $provider = new ArrayDataProvider([
            'allModels' => $this->getFakedModels(),
            'pagination' => [
                // Should not hard coded
                'pageSize' => $page_size,
            ],
            'sort' => [
                'attributes' => ['id'],
            ],
        ]);

        return $this->render('test', ['listDataProvider' => $provider]);
    }

    private function getFakedModels()
    {
        $rawData = [];

        for ($i=1; $i < 18; $i++) {
            $item = [
                'id' => $i,
                'title' => 'Title ' . $i,
            ];

            $rawData[] = $item;
        }

        return $rawData;
    }
}

Then use DemoPager with ListView in your view file.

<?php
// YOUR_APP/views/test/test.php

use yii\widgets\ListView;
?>

<?= ListView::widget([
    'options' => [
        'tag' => 'div',
    ],
    'dataProvider' => $listDataProvider,
    'itemView' => function ($model, $key, $index, $widget) {
        return '<div>' . $model['title'] . '</div>';
    },
    'itemOptions' => [
        'tag' => false,
    ],
    'summary' => '',
    'layout' => '{items} {pager}',

    'pager' => [
        // Use custom pager widget class
        'class' => frontend\widgets\demopager\DemoPager::className(),

        // Configurations for default LinkPager widget are still available
        'firstPageLabel' => 'First',
        'lastPageLabel' => 'Last',
        'maxButtonCount' => 4,
    ],

]);
?>

Check the result by accessing URL: http://YOUR_HOST_OR_APP/index.php?r=test/test.

 

11 Style

You may discover that our DemoPager is ugly now because there's no CSS style yet.

There are two ways to style DemoPager widget.

  • Write CSS styles in YOUR_APP/widgets/demopager/assets/css/demopager.css, and package this CSS file together with DemoPager widget class. But this way lose flexibility if widget user want to add dynamic styles;
  • Pass CSS class name or CSS styles directly as HTML options while using DemoPager widget.

I'll demonstrate the second method here.

<?php
// YOUR_APP/views/test/test.php

use yii\widgets\ListView;
?>

<div class="row">
    <?= ListView::widget([
        'options' => [
            'tag' => 'div',
        ],
        'dataProvider' => $listDataProvider,
        'itemView' => function ($model, $key, $index, $widget) {
            return '<div>' . $model['title'] . '</div>';
        },
        'itemOptions' => [
            'tag' => false,
        ],
        'summary' => '',
        'layout' => '{items} {pager}',

        'pager' => [
            // Use custom pager widget class
            'class' => frontend\widgets\demopager\DemoPager::className(),

            // Configurations for default LinkPager widget are still available
            'firstPageLabel' => 'First',
            'lastPageLabel' => 'Last',
            'maxButtonCount' => 4,
        ],

        // Options for <ul> wrapper of default pager buttons are still available
        'options' => [
            'class' => 'pagination',
            'style' => 'display:inline-block;float:left;margin:20px 10px 20px 0;width:auto;'
        ],

        // Style for page size select
        'sizeListHtmlOptions' => [
            'class' => 'form-control',
            'style' => 'display:inline-block;float:left;margin:20px 10px 20px 0;width:auto;'
        ],

        // Style for go to page input
        'goToPageHtmlOptions' => [
            'class' => 'form-control',
            'style' => 'display:inline-block;float:left;margin:20px 10px 20px 0;width:auto;'
        ],
    ]);
    ?>
</div>

That's all, now our DemoPager looks really good. Source codes are available.