Как правильно делать pjax-формы?

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
Brainfuck
Сообщения: 260
Зарегистрирован: 2018.02.19, 14:20

Как правильно делать pjax-формы?

Сообщение Brainfuck » 2019.01.21, 12:07

У меня есть две модели: Catalog(id, name) и CatalogItem(id, catalog_id, value). Я сделал для них вот такой CRUD-контроллер и хотел-бы всю логику по управлению ими уместить на одной pjax-странице (просто уж больно мало полей - по одному текстовому и все - нет смысла делать отдельные страницы).

Контроллер:

Код: Выделить всё

<?php

namespace app\controllers;

use app\models\Catalog;
use app\models\CatalogItem;
use MP\DIMagick\ActionDITrait;
use yii\filters\AccessControl;
use yii\web\Controller;

class CatalogController extends Controller {
    use ActionDITrait;

    public function behaviors() {
        return [
            'access' => [
                'class' => AccessControl::class,
                'rules' => [
                    [
                        'actions' => ['list', 'save', 'delete', 'create-item', 'delete-item'],
                        'allow' => true,
                        'roles' => ['@'],
                    ],
                ],
            ],
        ];
    }

    public function actionList() {
        return $this->render('list', ['items' => Catalog::find()->with('items')->all()]);
    }

    public function actionSave($id = null) {
        $model = $id ? Catalog::findOne($id) : new Catalog();

        if ($model->load(\Yii::$app->request->post()))
            $model->save();
    }

    public function actionDelete(Catalog $model) {
        $model->delete();
    }

    public function actionCreateItem() {
        $model = new CatalogItem();

        if ($model->load(\Yii::$app->request->post()))
            $model->save();
    }

    public function actionDeleteItem(CatalogItem $model) {
        $model->delete();
    }
}
Страница list:

Код: Выделить всё

<?php
/** @var \app\models\Catalog[] $items */

use app\models\Catalog;
use app\models\CatalogItem;
use rmrevin\yii\fontawesome\FAS;
use yii\bootstrap\Modal;
use yii\data\ArrayDataProvider;
use yii\helpers\Html;
use yii\widgets\ActiveForm;
use yii\widgets\ListView;
use yii\widgets\Pjax;

$this->title = 'Справочники';
$this->params['breadcrumbs'] = [$this->title];
?>
<style>
    .list-view > div {
        padding: 10px;
    }

    .list-view > div:nth-child(odd) {
        background-color: lightskyblue;
    }

    .list-view > div:nth-child(even) {
        background-color: lightyellow;
    }
</style>

<? Pjax::begin(['enablePushState' => false]); ?>
    <? $form = ActiveForm::begin(['action' => ['catalog/save'], 'options' => ['data-pjax' => true, 'class' => 'row']]); ?>
        <div class="col-md-4"><?= $form->field(new Catalog, 'name')->label(false)->input('text', ['placeholder' => 'Название']); ?></div>
        <div class="col-md-2"><input type="submit" value="Добавить" class="btn btn-primary" style="margin-left: -20px"></div>
    <? ActiveForm::end(); ?>

    <div style="border: 1px solid lightgrey">
        <?= ListView::widget([
            'dataProvider' => new ArrayDataProvider(['allModels' => $items, 'pagination' => false]),
            'layout' => '{items}',
            'itemView' => function(Catalog $catalog) {
                ob_start();
                echo $catalog->name;
                echo Html::beginTag('span', ['class' => 'pull-right']);
                    Modal::begin([
                        'toggleButton' => ['tag' => 'a', 'label' => FAS::i('edit'), 'style' => 'cursor: pointer'],
                        'header' => Html::tag('h3', 'Варианты'),
                    ]);
                        $form = ActiveForm::begin(['action' => ['catalog/create-item'], 'options' => ['data-pjax' => true, 'class' => 'row']]);
                            echo Html::tag('div', $form->field(new CatalogItem(['catalog_id' => $catalog->id]), 'value')->label(false), ['class' => 'col-md-6']);
                            echo '<div class="col-md-2"><input type="submit" value="Добавить" class="btn btn-primary" style="margin-left: -20px"></div>';
                        ActiveForm::end();

                        echo ListView::widget([
                            'dataProvider' => new ArrayDataProvider(['allModels' => $catalog->items, 'pagination' => false]),
                            'layout' => '{items}',
                            'itemView' => function(CatalogItem $catalogItem) {
                                return $catalogItem->value . Html::a(FAS::i('trash-alt'), ['catalog/delete-item', 'id' => $catalogItem->id], ['title' => 'Удалить', 'class' => 'pull-right', 'data-pjax' => true]);
                            }
                        ]);
                    Modal::end();

                    echo Html::a(FAS::i('trash-alt'), ['catalog/delete', 'id' => $catalog->id], ['title' => 'Удалить', 'data' => ['confirm' => 'Вы уверены что хотите удалить этот справочник?', 'pjax' => true]]);
                echo Html::endTag('span');
                return ob_get_clean();
            },
        ]) ?>
    </div>
<? Pjax::end(); ?>
Что я делаю не так? Ну не работает асинхронность от слова вообще. При нажатии ссылок или кнопок отправки формы все переходит по соответствующей ссылке (котроллер/действие). А мне надо чтобы все работало без перезагрузки страницы. Кажется я просто плохо понял как весь этот pjax работает... Доки читал.

Brainfuck
Сообщения: 260
Зарегистрирован: 2018.02.19, 14:20

Re: Как правильно делать pjax-формы?

Сообщение Brainfuck » 2019.01.21, 14:09

Начинаю понимать как это работает, но все равно как-то пока плохо... Вот я разнес элементы по отдельным вьюхам:

Код: Выделить всё

<?php

namespace app\controllers;

use app\models\Catalog;
use app\models\CatalogItem;
use MP\DIMagick\ActionDITrait;
use yii\filters\AccessControl;
use yii\web\Controller;

class CatalogController extends Controller {
    use ActionDITrait;

    public function behaviors() {
        return [
            'access' => [
                'class' => AccessControl::class,
                'rules' => [
                    [
                        'actions' => ['list', 'save', 'delete', 'create-item', 'delete-item'],
                        'allow' => true,
                        'roles' => ['@'],
                    ],
                ],
            ],
        ];
    }

    public function actionList() {
        return $this->render('index');
    }

    public function actionSave($id = null) {
        $model = $id ? Catalog::findOne($id) : new Catalog;

        if ($model->load(\Yii::$app->request->post()))
            $model->save();

        return $this->renderPartial('catalogs');
    }

    public function actionDelete(Catalog $model) {
        $model->delete();
        return $this->renderPartial('catalogs');
    }

    public function actionCreateItem() {
        $model = new CatalogItem;

        if ($model->load(\Yii::$app->request->post()))
            $model->save();

        return $this->renderPartial('items', ['model' => $model->catalog]);
    }

    public function actionDeleteItem(CatalogItem $model) {
        $model->delete();
        return $this->renderPartial('items', ['model' => $model->catalog]);
    }
}
index

Код: Выделить всё

<?php
use yii\widgets\Pjax;

$this->title = 'Справочники';
$this->params['breadcrumbs'] = [$this->title];
?>
<style>
    .list-view > div {
        padding: 10px;
    }

    .list-view > div:nth-child(odd) {
        background-color: #71e3fa;
    }

    .list-view > div:nth-child(even) {
        background-color: #fff690;
    }
</style>

<? Pjax::begin(['enablePushState' => false]); ?>
    <?= $this->render('catalogs') ?>
<? Pjax::end(); ?>
catalogs

Код: Выделить всё

<?php
use app\models\Catalog;
use rmrevin\yii\fontawesome\FAS;
use yii\bootstrap\Modal;
use yii\data\ArrayDataProvider;
use yii\helpers\Html;
use \yii\widgets\ActiveForm;
use yii\widgets\ListView;
?>

<? $form = ActiveForm::begin(['action' => ['catalog/save'], 'options' => ['data-pjax' => true, 'class' => 'row']]); ?>
    <div class="col-md-4"><?= $form->field(new Catalog, 'name')->label(false)->input('text', ['placeholder' => 'Название']); ?></div>
    <div class="col-md-2"><input type="submit" value="Добавить" class="btn btn-primary" style="margin-left: -20px"></div>
<? ActiveForm::end(); ?>

<div style="border: 1px solid lightgrey">
    <?= ListView::widget([
        'dataProvider' => new ArrayDataProvider(['allModels' => Catalog::find()->with('items')->all(), 'pagination' => false]),
        'layout' => '{items}',
        'itemView' => function(Catalog $catalog) {
            ob_start();
            echo $catalog->name;
            echo Html::beginTag('span', ['class' => 'pull-right']);
                Modal::begin([
                    'toggleButton' => ['tag' => 'a', 'label' => FAS::i('edit'), 'style' => 'cursor: pointer'],
                    'header' => Html::tag('h3', 'Варианты'),
                ]);
                    echo $this->render('items', ['model' => $catalog]);
                Modal::end();

                echo Html::a(FAS::i('trash-alt'), ['catalog/delete', 'id' => $catalog->id], ['title' => 'Удалить', 'data' => [/*'confirm' => 'Вы уверены что хотите удалить этот справочник?',*/ 'pjax' => true]]);
            echo Html::endTag('span');
            return ob_get_clean();
        },
    ]) ?>
</div>
items

Код: Выделить всё

<?php
/** @var \app\models\Catalog $model */

use app\models\CatalogItem;
use yii\data\ArrayDataProvider;
use yii\widgets\ActiveForm;
use yii\widgets\ListView;
use yii\widgets\Pjax;

$newModel = new CatalogItem(['catalog_id' => $model->id]);
?>

<? Pjax::begin(['enablePushState' => false]) ?>
    <? $form = ActiveForm::begin(['action' => ['catalog/create-item'], 'options' => ['data-pjax' => true, 'class' => 'row']]); ?>
        <?= $form->field($newModel, 'catalog_id', ['options' => ['style' => 'display: none']])->hiddenInput()->label(false); ?>
        <div class="col-md-6">
            <?= $form->field($newModel, 'value')->label(false) ?>
        </div>
        <div class="col-md-2">
            <input type="submit" value="Добавить" class="btn btn-primary" style="margin-left: -20px">
        </div>
    <? ActiveForm::end(); ?>

    <?= ListView::widget([
        'dataProvider' => new ArrayDataProvider(['allModels' => $model->items, 'pagination' => false]),
        'layout' => '{items}',
        'itemView' => 'single_item',
    ]); ?>
<? Pjax::end() ?>
single_item

Код: Выделить всё

<?php
/** @var \app\models\CatalogItem $model */

use rmrevin\yii\fontawesome\FAS;
use yii\helpers\Html;

echo $model->value;
echo Html::a(FAS::i('trash-alt'), ['catalog/delete-item', 'id' => $model->id], ['title' => 'Удалить', 'class' => 'pull-right', 'data-pjax' => true]);
Сейчас когда я пытаюсь добавить Catalog то мне почему-то рендерит список элементов самого первого справочника. А когда удаляю вообще Forbidden выбивает, но при этом удаляет. Когда пытаюсь добавить CatalogItem почему-то добавляются сразу два одинаковых элемента и рендерит список их вне модального окна. А когда удаляю - то тоже удаляет, но Forbidden кидает. Что за хрень творится? Помогите пожалуйста!!!

Brainfuck
Сообщения: 260
Зарегистрирован: 2018.02.19, 14:20

Re: Как правильно делать pjax-формы?

Сообщение Brainfuck » 2019.01.22, 10:16

Неужели никто не может помочь? :(

andku83
Сообщения: 988
Зарегистрирован: 2016.07.01, 10:24
Откуда: Харьков

Re: Как правильно делать pjax-формы?

Сообщение andku83 » 2019.01.22, 12:57

pjax в ответе ожидает найти .pjax-container со своим id, в вашем случае в actionSave отсутствует контейнер и срабатывает редрект.
Любой виджет если ему не задать id генерирует его динамически и каждый следующий на странице будет иметь следующую нумерацию, вот поэтому когда вы рендерите только второй он имеет id первого.
catalogs

Код: Выделить всё

<?php Pjax::begin([
    'id' => 'pjax-main',
    'enablePushState' => false
]); ?>
<?php $form = ActiveForm::begin(['action' => ['catalog/save'], 'options' => ['data-pjax' => true, 'class' => 'row']]); ?>
...
<?php Pjax::end(); ?>
items

Код: Выделить всё

<?php Pjax::begin([
	'id' => 'pjax-create-item',
	'enablePushState' => false
]) ?>
    <?php $form = ActiveForm::begin(['action' => ['catalog/create-item'], 'options' => ['data-pjax' => true, 'class' => 'row']]); ?>
...
<?php Pjax::end() ?>
Brainfuck писал(а):
2019.01.22, 10:16
Неужели никто не может помочь? :(
создавая дубли тем, и "прилично" общаясь вы думаете что быстрее получите ответ?!

срочные вопросы иногда быстрее можно решить в телеге https://t.me/yii2ru (правда pjax там сразу зафукают ;))

Brainfuck
Сообщения: 260
Зарегистрирован: 2018.02.19, 14:20

Re: Как правильно делать pjax-формы?

Сообщение Brainfuck » 2019.01.22, 13:20

andku83 писал(а):
2019.01.22, 12:57
pjax в ответе ожидает найти .pjax-container со своим id, в вашем случае в actionSave отсутствует контейнер и срабатывает редрект.
Любой виджет если ему не задать id генерирует его динамически и каждый следующий на странице будет иметь следующую нумерацию, вот поэтому когда вы рендерите только второй он имеет id первого.
Не помогло. :( Первая форма добавления каталогов срабатывает вроде-бы - страница не перезагружается, элемент добавляется, правда при этом у него не открывается модальное окно с элементами. А ссылка на удаление все-также редиректит.
andku83 писал(а):
2019.01.22, 12:57
создавая дубли тем, и "прилично" общаясь вы думаете что быстрее получите ответ?!

срочные вопросы иногда быстрее можно решить в телеге https://t.me/yii2ru (правда pjax там сразу зафукают ;))
Просто я не знал где еще спросить, а сам сколько не гуглю и дебажу - ничего не выходит. :(
andku83 писал(а):
2019.01.22, 12:57
правда pjax там сразу зафукают ;)
А что с ним не так? Типа - фигачь на реакте?)) Я просто не фронтендер и с js дружу только на уровне jquery. Поэтому pjax показался мне хорошим выходом.
Последний раз редактировалось Brainfuck 2019.01.22, 15:03, всего редактировалось 1 раз.

Brainfuck
Сообщения: 260
Зарегистрирован: 2018.02.19, 14:20

Re: Как правильно делать pjax-формы?

Сообщение Brainfuck » 2019.01.22, 15:31

Brainfuck писал(а):
2019.01.22, 15:28
Так! Со ссылками я нашел решение - надо указать data-method в post. Тогда все нормально. Но с формами по прежнему проблема...
Что же не так с формами? Первая проблема: при добавлении каталога у полученного элемента не работает модальное окно. При нажатии на кнопку, открывающую его, все затемняется как при открытии модального окна и становится некликабельным, но самого окна нет. Вторая проблема: при добавлении элемента каталога в модальном окне опять же все становится некликабельным, окно пропадает и я вижу что там отобразились элементы того каталога в который добавлял, и все - без самих каталогов.

andku83
Сообщения: 988
Зарегистрирован: 2016.07.01, 10:24
Откуда: Харьков

Re: Как правильно делать pjax-формы?

Сообщение andku83 » 2019.01.23, 12:35

Brainfuck писал(а):
2019.01.22, 13:20
Не помогло. :( Первая форма добавления каталогов срабатывает вроде-бы - страница не перезагружается, элемент добавляется, правда при этом у него не открывается модальное окно с элементами. А ссылка на удаление все-также редиректит.
Причина в $this->renderPartial, используйте $this->renderAjax.
Насчет редиректа смотрите в дебагере, возможно там ошибка, вы же модель удалили и передаете ее на рендер...
Brainfuck писал(а):
2019.01.22, 13:20
А что с ним не так? Типа - фигачь на реакте?)) Я просто не фронтендер и с js дружу только на уровне jquery. Поэтому pjax показался мне хорошим выходом.
pjax старая неподдерживаемая библиотека на jquery (имеющая баги и сложность в понимании при нестандартных задачах)

Brainfuck
Сообщения: 260
Зарегистрирован: 2018.02.19, 14:20

Re: Как правильно делать pjax-формы?

Сообщение Brainfuck » 2019.01.23, 13:11

andku83 писал(а):
2019.01.23, 12:35
Причина в $this->renderPartial, используйте $this->renderAjax.
Уже пробовал - не помогает. Вы вообще смотрели их реализацию? renderPartial рендерит и возвращает только саму вьюху, а renderAjax дополнительно оборачивает ее в beginPage, head, beginBody. По идее в данном случае как раз нужен renderPartial, т.к. в контейнер надо вставить именно частичную вьюху безо всяких там html, head, body...
andku83 писал(а):
2019.01.23, 12:35
Насчет редиректа смотрите в дебагере, возможно там ошибка, вы же модель удалили и передаете ее на рендер...
Говорю же уже все по 100 раз продебажил! Да и где вы увидели такое? В actionDelete я вообще ничего не передаю во вьюху - там внутри делается новый запрос к базе, а в actionDeleteItem $model->catalog после удаления $model вполне себе работает - проверено. Метод delete делает запрос на удаление элемента каталога к базе, но никак не затирает catalog_id в самом объекте и ничто не мешает продолжать получать его свойства.
andku83 писал(а):
2019.01.23, 12:35
pjax старая неподдерживаемая библиотека на jquery (имеющая баги и сложность в понимании при нестандартных задачах)
А чего же ее еще не задепрекейтили в Yii? И что предлагается как альтернатива? Реакт? Не, спасибо. Мне вот чем нравится Yii так это виджетами. Ни в одном фреймворке ни на одном языке нет такого набора. Можно совсем не умея верстать делать нормальный интерфейс. Пусть он будет везде одинаково выглядеть, зато функционально. Я работаю в небольшой конторе над проектом в одиночку. Сам я чистый бэкэндщик и почти совсем не умею в js выше чем jquery. Поэтому виджеты yii (в частности такие как pjax) - для меня настоящее спасение. А есть ведь и настолько большие виджеты (например этот), что наверное даже умеющий во фронте человек его будет верстать долго, а тут виджет вставил и готово - красота же. Я считаю это главным преимуществом Yii.

P.S. По сути виджеты - это аналог реактовских компонентов. Они же там тоже переиспользуемые. Так-то да - надо осваивать реакт, но пока некогда.

Аватара пользователя
proctoleha
Сообщения: 276
Зарегистрирован: 2016.07.10, 19:00

Re: Как правильно делать pjax-формы?

Сообщение proctoleha » 2019.01.23, 15:34

Brainfuck писал(а):
2019.01.23, 13:11
andku83 писал(а):
2019.01.23, 12:35
pjax старая неподдерживаемая библиотека на jquery (имеющая баги и сложность в понимании при нестандартных задачах)
А чего же ее еще не задепрекейтили в Yii? И что предлагается как альтернатива?
А почему кто-то должен что-то предлагать? Это open source. В платных продуктах мы пинаем ТП, а здесь кто кому чем обязан? Если не работает пиджак пишем все на обычном jquery, в чем проблема? Повесить обработчик на элемент DOM, получить html, и вставить его в нужное место.

И к слову о pjax - не раз писалось, что в след. версии yii он будет выпилен.
Вот за что я не люблю линукс, так это за свои кривые, временами, руки

Brainfuck
Сообщения: 260
Зарегистрирован: 2018.02.19, 14:20

Re: Как правильно делать pjax-формы?

Сообщение Brainfuck » 2019.01.23, 16:08

proctoleha писал(а):
2019.01.23, 15:34
Brainfuck писал(а):
2019.01.23, 13:11
andku83 писал(а):
2019.01.23, 12:35
pjax старая неподдерживаемая библиотека на jquery (имеющая баги и сложность в понимании при нестандартных задачах)
А чего же ее еще не задепрекейтили в Yii? И что предлагается как альтернатива?
А почему кто-то должен что-то предлагать? Это open source. В платных продуктах мы пинаем ТП, а здесь кто кому чем обязан? Если не работает пиджак пишем все на обычном jquery, в чем проблема? Повесить обработчик на элемент DOM, получить html, и вставить его в нужное место.

И к слову о pjax - не раз писалось, что в след. версии yii он будет выпилен.
Не обязан, но чтобы фреймворк оставался актуальным надо предлагать его юзерам альтернативы убранным возможностям. Кстати не такой уж pjax и непопулярный. Вон тот же kartik в куче виджетов его использует для асинхронности. Если уж на то пошло то bootstrap и jquery во фронте уже в принципе устарели (даже я как бэкэндщик про это слышал). А в yii на них все построено. Ну следующая версия версия - это как я понимаю 3+. Там уже будет считай другой фреймворк. Как и при переходе с 1 на 2.

P.S. С jquery возиться - последнее средство. Потом такой говнокод будет что сам на это смотреть не захочешь. Предпочитаю решать все виджетами без единой строчки js.

Ответить