yiisoft / yii2

Yii 2: The Fast, Secure and Professional PHP Framework
http://www.yiiframework.com
BSD 3-Clause "New" or "Revised" License
14.24k stars 6.91k forks source link

Strange behavior of the Gridview column filter. #18060

Closed alejosv closed 4 years ago

alejosv commented 4 years ago

What steps will reproduce the problem?

  1. Create a simple gridview wrapped with a pjax in the view and enable a some actions, in this case "status"

    
    <?php Pjax::begin([
        'id' => 'clients',
        'enablePushState' => false
    ]); ?>
    
        <?= GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'tableOptions' => [
            'class' => 'table table-striped table-bordered',
        ],
        'options' => [
            'class' => 'table-responsive',
        ],
        'columns' => [
             ...
             [
                'attribute' => 'name',
                'label' => 'Client Name',
                'filterInputOptions' => [
                    'class' => 'form-control',
                    'autocomplete' => 'off'
                ]
            ],
            [
                'attribute' => 'headquarters',
                'label' => 'Headquarter',
                'filterInputOptions' => [
                    'class' => 'form-control',
                    'autocomplete' => 'off'
                ]
            ],
            [
                'attribute' => 'area',
                'label' => 'Area',
                'filterInputOptions' => [
                    'class' => 'form-control',
                    'autocomplete' => 'off'
                ]
            ],
            ...
            [
                'class' => 'yii\grid\ActionColumn',
                'header'=>'Acción', 
                'headerOptions' => ['width' => '200'],
                'template' => '{status} {edit} {delete} {options}',
                'buttons' => [
                    ...
                    'status' => function($url, $model, $key) {     
                        return Html::a($model->enabled == 1 ? '<i class="fa fa-times-circle-o"></i>' : '<i 
                            class="fa fa-check-circle-o"></i>', $url, [
                            'class' => $model->enabled == 1 ? 'btn btn-warning' : 'btn btn-success', 
                            'data-pjax' => 'w0',
                            'title' => Yii::t('app', 'Change client status'),
                        ]);
                    },
                    ...
                ]

<?php Pjax::end(); ?>

2. Create in the controller a action 
```php
<?php

namespace app\controllers;

use Yii;
use yii\web\{Controller, NotFoundHttpException};
use yii\filters\{VerbFilter, AccessControl};
use yii\helpers\ArrayHelper;
use yii\db\Query;

use app\models\{Clients, ClientsSearch};
use app\components\{CountryHelper, ClientsHelper};

class ClientsController extends Controller
{
    ....
    public function actionStatus($id)
        {
            $client = \app\models\Clients::findOne($id);
            $client->enabled = $client->enabled ? 0 : 1;
            $client->save(false);

            $searchModel = new ClientsSearch();
            $dataProvider = $searchModel->search(Yii::$app->request->queryParams);
            $dataProvider->setPagination(['pageSize' => 10]);

            return $this->renderPartial('index', [
                'searchModel' => $searchModel,
                'dataProvider' => $dataProvider,
            ]);
    }
    ....
}
  1. Try to search a client by name and after lost focus in this case the filter the search work perfect, but if you click on the status button the action (work perfect) and after try search a client name again, the action that is trigger inclues the last excuted action. The calls would look like this:

  2. The search by filter before click on status boton: example.com/clients?ClientsSearch%5Bnit%5D=&ClientsSearch%5Bname%5D=cajam&ClientsSearch%5Bheadquarters%5D=&ClientsSearch%5Barea%5D=&ClientsSearch%5Benabled%5D=&_pjax=%23clients

  3. The search by filter after click on status boton: example.com/clients/status/2ed10669-74d7-4682-a4ed-30e991c2a5de?ClientsSearch%5Bnit%5D=&ClientsSearch%5Bname%5D=camam&ClientsSearch%5Bheadquarters%5D=&ClientsSearch%5Barea%5D=&ClientsSearch%5Benabled%5D=&_pjax=%23clients

What is the expected result?

After the click on status button the URL of search by filter not includes the last action, instead keep the same url.

What do you get instead?

The search filters well, but also triggers the action in the controller, which is quite strange.

Additional info

Q A
Yii version 2.0.14
PHP version PHP 7.3.17
Operating system Ubuntu 18.04
samdark commented 4 years ago

Does anything alike happen without PJAX?

alejosv commented 4 years ago

If I remove pjax the view is render without the layout because I have the return with renderPartial and the URL is example.com/clients/status/1a2b3c4d, which seems correct to me because I called the status action. The filters doesn't work because the js files are not loading.

If I return a render instead a partialRender, the layout is load but the url change of example.com/clients to example.com/clients/status/a1b2c3d4 and the columns filters includes this URL into search call this way: example.com/clients/status/a1b2c3d4?ClientsSearch%5Bnit%5D=&ClientsSearch%5Bname%5D=cajam&ClientsSearch%5Bheadquarters%5D=&ClientsSearch%5Barea%5D=&ClientsSearch%5Benabled%5D=

I saw this in the assets/yii.gridView.js:

https://github.com/yiisoft/yii2/blob/2a73a4ae3a86a68e629e68262ea14c24787027b6/framework/assets/yii.gridView.js#L121

The settings object have a key called filterUrl and this value change when I called the action "status". IMHO the value in this property should not change if the action is called via ajax since affect the filters columns search action, but I don't know, I didn't a debug to make sure about this.

alex-code commented 4 years ago

Setting the filterUrl yourself should work.

alejosv commented 4 years ago

@alex-code so, is it considered correct behavior? The filterUrl documentation says:

The URL for returning the filtering result. [yii\helpers\Url::to()](https://www.yiiframework.com/doc/api/2.0/yii-helpers-baseurl#to()-detail) will be called to normalize the URL. If not set, the current controller action will be used. When the user makes change to any filter input, the current filtering inputs will be appended as GET parameters to this URL.

My current controller action in this case is clients/index . According to the documentation I should not change the filterUrl. The Gridview should work with default setting unless the URL for the filters is not in the current controller.

alex-code commented 4 years ago

Your actionStatus renders the index view so that'll be the route used.

Can you redirect to your index action after the save?

alejosv commented 4 years ago

Yes, I really ended up doing it that way, but I am left with the question of whether I use the filters of the gridview wrapped in pjax and execute an action, this last action changes the filterUrl.

I understand that I am calling the action status from a widget using ajax, but in these cases "ajax" the filterUrl should not be considered in the gridview. One of two things may be happening:

  1. My implementation is totally wrong.
  2. If from a row of a gridview wrapped in pjax I make a call to an action without redirecting but rendering partially, the filter url configuration should not change with the last action because I am rendering the index.

If I finally have to use a redirect there is no point in using pjax in the view even if I want to.

alex-code commented 4 years ago

The filterUrl will be based off the current route.

So if you're on your index it'll be /clients. But if you call actionStatus it'll be /clients/status/....

Either redirecting back to index or setting filterUrl would get around this.

samdark commented 4 years ago

Alright. @alex-code thanks for explanation. Closing it.