Отличнейший модуль Gii CRUD для генерации кода одностаничных ajax-приложений на Yii2 framework.
Демонстрация: http://tfb7950x.bget.ru/application/web/index.php?r=film
- Добавление, чтение, удаление, обновление записей без перезагрузки страницы методом ajax
- Возможность удалять записи пачками
- Используется ajax, а не pjax (поддерживает даже IE8)
- Имеется пейджер и возожность выставлять число записей на страницу (чего нет ни в одном из extensions для gii)
- Возможность сортировать по нескольким колонкам одновременно, т.е. например, сперва отсортировать по должностями, а потом каждую группу отсортировать по фамилии
- Возможность сортировать по связанным таблицам (как это сделать будет показано ниже)
- При поиске в текстовом поле не нужно нажимать Enter, поиск запускается автоматически, когда пользователь перестает печатать
- Имеются горячие клавиши (Insert, Ctrl+Enter, Ctrl+Delete, Esc, соответственно: добавить, сохранить, удалить, отмена)
- Клавиша Esc очищает текстовое поле в поиске (мелочь, но все равно приятно)
- Выводит ошибки валидации непосредственно от сервера, а не от клиентских скриптов (т.е. ajax-ом можно вывести ошибку, например, о неправильном пароле и т.д.)
- Выводит на экран полученные данные если они не соответствуют заданному формату, как правило, это внутренние ошибки сервера (если этого не делать, то ошибку можно просто упустить из вида, и, даже если ее заметишь, то приходится открывать отладчик)
- Прост в освоении, сгенерированный код легко использовать для создания ajax-страничек.
- При использовании jQuery 3 и выше перестаёт работать перемещение формы на тачскринах (мобильных устройств). Поэтому рекомендуется использовать jQuery 1 или 2.
- Добавить возможность показывать детальную информацию прямо внутри таблицы (кнопка "expand", как здесь: http://demos.krajee.com/grid-demo)
- Сделать экспорт в html, csv, text, excel, pdf, json
Скачивается с помощью composer. В папке приложения в файле composer.json дописать строчку:
"require": {
"alhimik1986/yii2_crud_module": "^1.0"
},
или в командой строке ввести:
$ composer require alhimik1986/yii2_crud_module
Затем в файле config/web.php добавить следующие строчки:
// Добавляю генератор кода ajax_form_crud
if (YII_ENV_DEV) {
$config['modules']['gii']['generators']['ajax_form_crud_generator'] = [
'class' => 'alhimik1986\yii2_crud_module\generators\crud\Generator',
'templates' => [
'ajax_form_template' => '@vendor/alhimik1986/yii2_crud_module/generators/crud/default',
],
];
}
Прейдите по адресу: http://localhost/index.php?r=gii или http://localhost/gii . Далее перейдите по ссылке "AjaxForm CRUD Generator".
Заглянем в файл views/@conroller_id/index/_js_table.php. В нем мы видим, что-то вроде:
new ajaxForm({
...
});
Так вот, в этот "экземпляр класса" передаются параметры. Эти параметры перезаписывают параметры по умолчанию. Параметры по умолчанию можно увидеть в библиотеке ajax-form.js, назначение каждого параметра указано в комментариях. Значения, переданные в экземпляр этого класса, перезаписывают параметрами по умолчанию, и помещаются в переменную "settings". Переменная "settings" часто используется и передается во многие функции в виде аргумента. Кстати, параметры по умолчанию содержат почти все системные функции библиотеки ajax-form.js, поэтому, передавая параметры в экземпляр можно полностью менять поведение этой библиотеки.
Чтобы было понятно, как все устроено, я расскажу философию этой библиотеки.
Давайте представим как бы работал CRUD, если бы мы его делали своими руками при помощи jQuery:
- Нужно указать селектор и событие, которое вызовет появление формы. В нашем случае это кнопка "добавить" (.ajax-form-button-create) и событие "click".
- При срабатывании события нужно отправить ajax-запрос, чтобы получить содержимое формы и вывести эту форму в определенное место
- Полученной форме нужно задать положение по центру экрана, сделать ее перемещаемой и растягиваемой (обработка формы 1)
- Полученной форме нужно привязать события для "кнопки отправки данных с формы" и кнопок для отмены (обработка фомры 2)
- Когда срабатывает событие оптправки данных с формы, нужно отправить форму и получить один из результатов (внутренняя ошибка сервера, ошибка валидации, или успешный результат)
- В случае успешного результата нужно удалить форму и отправить ajax-запрос, чтобы обновить таблицу
Для всего этого нам потребуются 3 ajax-запроса с функциями обратного вызова (callbacks), которые вызываются одна за другой только в случае успешного результата. Но, т.к. нам еще нужно передать им параметры (например, селектор кнопки, url запроса и т.д.), то заключим их в 3 соответствующих объекта:
- Для появления формы, назовем его create
- Для отправки формы, назовем его submit
- Для обновления таблицы после успешной отправки формы, назовем его afterSubmit.
Схематично в коде они будут выглядеть так:
new ajaxForm({
create: {
...
},
submit: {
...
},
afterSubmit: {
...
},
});
Каждый объект будет содержать некоторые поля, давайте прикинем, какие. Нам нужно указать условия, при которых нам нужно вызывать ajax-запросы, как правило, это клик по кнопке. Соответственно, нам нужно указать селектор (например, .ajax-form-button-create) и тип события (как правило, "click"). Также, при желании можно указать делегатор, но по умолчанию стоит document, так что, если его не указывать, то ничего страшного не произойдет. Таким образом, поля будут иметь следующие названия: "delegator", "selector", "on" - соответственно, делегатор селектор и событие, в jQuery они будут выглядеть вот так:
$(delegator).on(on, selector, function(){
});
При срабатывании условий делается ajax-запрос, т.е. нам еще нужно указать параметры этого запроса. Параметры задаются в поле "ajax". Имена параметров совпадают с именами параметров в обычной jQuery методе $.ajax({}). Но для большей гибкости в поле "ajax" нужно передавать не объект, а функцию, которая возвращает объект. Если функция вернет значение false, то ajax-запрос не будет выполнен, а вместе с ним и не будет выполняться последующая цепочка (submit и afterSubmit). Схематично в коде все будет выглядеть примерно так:
new ajaxForm({
create: {
delegator: document,
selector: '.ajax-form-button-create',
on: 'click',
ajax: function(settings) {
return {
url: 'http://localhost/controller_id/form'
};
}
},
submit: {
...
},
afterSubmit: {
...
},
});
Где settings, как уже говорилось ранее, - это смесь переданных параметров и параметров по умолчанию. Т.е., все, что мы передали в
ajaxForm, можно использовать в функциях обратного вызова. Иными словами через переменную settings мы имеем доступ к любому параметру.
Например, settings.create.selector - селектор, который мы задали. Чтобы иметь доступ к элементу, благодаря которому была вызвана
функция обратного вызвана, он находится в settings.create.$ . Как мы поняли, с ним можно работать как с обычным jQuery-элементом,
например, settings.create.$.css({color: 'green'});
.
В случае успешного запроса вызывается функция обратного вызова "success", имеющая аргументы data и settings, где data - принятые данные. Следует учесть, что data содержит только значение поля content, который содержится в ответной части. Формат ответной части описан ниже.
return $($(data)).appendTo('<?=$wrapper_selector?>');
Также следует учесть, что метод success должен возвращать jQuery-объект принятой формы. Далее этот объект будет содержаться в settings.form.$ и он требуется для внутренней обработки (например, позиционирование в центре экрана, сделать форму перемещаемой, привязывает события нажатия кнопки "Сохранить", "Отмена", "Закрыть" и т.д.)
Сразу после метода success, вызывается метод afterSubmit. Он нужен для последующей обработки формы, например, мы хотим, чтобы в нашей форме выпадающие списки были проинициированы плагином select2.js, а текстовые области (textarea) - WYSIWYG-редактором. Пример кода:
new ajaxForm({
create: {
...
afterSuccess: function(settings){
var $form = settings.form.$;
$form.find('select').select2();
$form.find('textarea').froalaEditor();
}
},
submit: {
...
},
afterSubmit: {
...
},
});
Если мы не хотим, чтобы наша форма была перемещаемой и растягиваемой, то нужно перезаписать метод _handleForm() в пустую функцию. Пример:
new ajaxForm({
create: {
...
_handleForm: function(){},
},
submit: {
...
},
afterSubmit: {
...
},
});
Таким же образом можно перезаписать любую другую системную функцию: _success, _afterSuccess и т.д. Подробнее узнать о назначении этих функциий можно, заглянув в ajax-form.js.
Объект submit имеет примерно те же "поля" и "методы":
new ajaxForm({
create: {
...
},
submit: {
// delegator: document, // Задать делегатор нельзя, делегатором является форма
selector: '.ajax-form-button-submit, .ajax-form-button-delete', // селектор кнопок, по нажатию которых происходит отправка данных формы
ajax: function(settings) {
var $form = settings.submit.$.parents('form');
// Если нажата кнопка "Удалить", то спрашивать подтверждение
if ( settings.submit.$.hasClass('ajax-form-button-delete')) {
if ( ! confirm('Удалить эту запись безвозвратно?'); ?>'))
return false; // отправка данных не произойдет
}
var $_return = {};
var url = $form.attr('action');
var data = $form.serializeArray();
// Если была нажата кнопка "Удалить", то меняем адрес и параметры запроса на те, которые удалят эту запись
if ( settings.submit.$.hasClass('ajax-form-button-delete')) {
url = <?= Url::to(['delete']); ?>';
data = {
<?= $className; ?>: {
id: settings.create.$.attr('data_id')
}
};
}
$_return['url'] = url;
$_return['data'] = data;
$_return['type'] = 'post';
return $_return;
}
},
afterSubmit: {
...
},
});
Если была нажата кнопка "Сохранить", то подставляем один адрес и параметры, а если нажата кнопка "Удалить", то подставляем другие адрес и параметры запроса.
Также есть методы: notValid(data, settings) - вызывается при ошибках валидации, и error(xhr, settings) - вызывается, если возникла внутренняя ошибка сервера. И у них есть соответствующие системные методы: _notValid(data, settings) и _error(xhr, exception). Соответственно, если мы хотим отключить вывод ошибок валидации или вывод внутренней ошибки сервера, то вот пример кода:
new ajaxForm({
submit: {
...
_notValid: function(){}, // отключаю вывод ошибок валидации
_error: function(){}, // отключаю вывод внутренней ошибки сервера
},
});
Если нам нужно выводить ошибки валидации не на всех полях, а только на одном первом попавшемся поле, то советую использовать такой код:
// Эффект тряски и навести фокус на указанный элемент
window.shakeAndFocus = function($elem) {
if ($elem.length > 0) {
$('html, body').stop().animate({scrollTop: $elem.offset().top - 90}, 200, function(){
<?php if ($this->isMobileOrTablet()): ?>
$elem.focus().shake(3, 5, 100);
<?php else: ?>
$elem.focus();
<?php endIf; ?>
});
}
};
new ajaxForm({
submit: {
...
notValid: function(data, settings){
var $form = settings.create.$.parents('form');
$form.find('.error:not(:first)').parent().html('');
$form.find('.input-error:not(:first)').removeClass('input-error');
$form.find('.label-error:not(:first)').removeClass('label-error');
$form.find('.has-error').removeClass('has-error');
// Получаю самое первое поле с ошибкой
var i, j, k, id;
for(i in data) {
for(j in data[i]) {
for(k in data[i][j]) {
id = i+'_'+j;
break;
}
break;
}
break;
}
var $input = $form.find('#'+id).first();
window.shakeAndFocus($input);
},
},
});
В Объекте afterSubmit используется гораздо меньше параметров. И он вызывается после submit и только в случае успешного результата.
new ajaxForm({
create: {
...
_handleForm: function(){},
},
submit: {
...
},
afterSubmit: {
ajax: function(settings) {
$(settings.form.selector).remove(); // Закрываю форму только после удачной записи и обновлении таблицы
$('<?=$table_id; ?>').trigger('search'); // Обновляю таблицу (поиск в таблице)
return false; // Не делать ajax-запрос, т.к. форма обновляется вызовом триггера "search"
},
success: function(data, settings) {}
},
});
По идее, мы должны были сделать ajax-запрос с заданным адресом и параметрами, чтобы обновить таблицу. Но есть способ попроще и он более правильный: нам нужно заново запустить поиск в таблице и никакого ajax-запроса посылать не нужно. Скрипт, который осуществляет поиск в таблице, находится в шаблоне @vendor/alhimik1986/yii2_js_view_module/views/jsPlugins/_ajaxTable.php Если в какой-то момент нужно запустить поиск в таблице, то в этой таблице нужно запустить триггер 'search':
$('#table_id').trigger('search');
Если вы не используете ActiveForm и у вас включен enableCsrfValidation, то сервер может ругаться, что ему не передают csrf-токены, чтобы этого не было, в ajaxForm можно передавать эти токены.
Часто нам нужно видеть процесс загрузки или обработки данных сервером. Для этого нам нужен индикатор загрузки, который появляется во время отправки ajax-запроса и исчезает после завершения этого запроса. В шаблоне _js_table.php мы указываем элемент, перед которым нужно расположить этот индикатор (поле loadingElem), и задаем css-стили этого индикатора (loadingStyle). Суммарно наши прочие объекты ajaxForm выглядят примерно так:
new ajaxForm({
loadingElem: loadingElem,
loadingStyle: loadingStyle,
csrf: csrf,
create: {
...
_handleForm: function(){},
},
submit: {
...
},
afterSubmit: {
...
},
});
При ajax-запросе сервер должен формировать ответ в формате JSON. Данный объект должен содержать следующие части:
{
status: 'success' | 'error', // статус ответа (success - результат успешный и цепочка вызовов будет продолжена, error - ошибка валидации)
content: 'text', // Содержимое ответа, которое передедается в аргумент data.
message: '', // flash-сообщения, которые используется в yii-фреймворке и задаются таким образом: Yii::$app->session->setFlash('success', 'Сохранено успешно!')
}
Чтобы не заморачиваться с форматом, имеется класс alhimik1986\yii2_crud_module\web\JsonController, который наследует стандартный контроллер yii\web\Controller. В нем уже встроены необходимые методы, с которыми мы будем работать для формирования ответа. Пример реализации можно увидеть в нашем сгенерированном контроллере. Пример:
return $this->renderJson('view_name', ['model' => $model]); // Тоже самое, что и $this->renderPartial('view_name', ['model'=>$model]), только в нужном формате для ajax-form.js. {status: 'success', content: 'содержимое вьюшки', message: ''}
return $this->checkErrorsAndDisplayResult($model); // Делает всю грязную работу: проверяет модель на наличие ошибок валидации и, если они есть, выводит их в нужном формате; если ошибок нет, то выводит {status: 'success', content: 'ok', message: ''}
Если модель будет содержать ошибки валидации, то ajax-form.js отыщет поля, в которых есть ошибки, и в родительском элементе
(.form-group .field-modelName-fieldName) добавит класс .has-error. Если в форме будет отсутствовать родительский класс
(.form-group .field-modelName-fieldName), то, на всякий случай, ajax-form.js в текстовых полях добавляет классс .input-error,
в их надписях (label) добавить класс .label-error, но, чтобы они подсвечивались красным цветом, нужно в css добавить
.input-error, .label-error {color: #a94442;}
Если же ответ сервера не будет соответствовать заданному формату (например, при внутренней ошибке сервера), то ajax-form.js выведет во всплывающее окно все содержимое, которое он получил, т.е. выводит на экран весь текст ошибки.
Да, имеется такая возможность. Для этого нужно сгенерировать отдельно каждый crud. Затем нужно будет объединить содержимое шаблонов
index.php в какой-то один, и подгрузить в него _js_table.php и _js_plugins.php с обоих crud-ов.
Далее, в шаблонах _js_table.php нужно найти методы, которые генерируют url-адреса: Url::to(['create'])
и добавить в адрес
соответствующие контроллеры, например, Url::to(['/myController/create'])
.
Помните, что эти crud-ы не мешают друг другу благодаря тому, что они находятся на разных $wrapper_selector
(см. _js_table.php),
т.е. разных тегах, которые имеют разные id.
И помните, что появляющаяся форма одного crud-а не удаляет форму другого crud-а, т.к. эти формы имеют уникальные id и они указаны в ajaxForm:
new ajaxForm({
form: {
selector: '#<?= $className; ?>-ajax-form',
},
create: {
...
},
submit: {
...
},
afterSubmit: {
...
},
});