I have very little AJAX's Information. That's why I asked my question here...
in yii2 how can create and submit form with ajax validation?
So I searched but could not find the right solution...
for example: (according to the official training site)
<?php $form = ActiveForm::begin([
'id' => $model->formName(),
'enableAjaxValidation' => true,
]); ?>
<?= $form->field($model, 'title')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'extra_txt')->textInput(['maxlength' => true]) ?>
<div class="form-group">
<?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>
and in controller:
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = 'json';
return ActiveForm::validate($model);
} else {
return $this->render('create', [
'model' => $model,
]);
}
My problems:
1- ajax validation
2- submit form with ajax
Why don't you use Pjax? Use yii\widgets\Pjax and add Pjax::begin() and Pjax::end() around your form.
A decent guide over here:
http://www.yiiframework.com/wiki/772/pjax-on-activeform-and-gridview-yii2/
Related
I have been developing a system to create a dynamic questioner, where an organizer can create questions for a user to respond, but I have been having trouble validating the answers of the users to the questioners. I have three types of inputs available for the organizer to create, a short answer, a long answer and an input field. I tried using the Each validation rule, but it doesn’t seem to be working. I search all around for an answer to this, but I didn't find anything that worked.
Here is my code.
View
<?php $form = ActiveForm::begin([ 'id' => 'respuesta-form', 'enableAjaxValidation' => true, 'options' => ['enctype' => 'multipart/form-data'], ]); ?>
<?php foreach ($preguntas as $i => $pregunta): ?>
<?php if($pregunta->tipo == 1): ?>
<?= $form->field($model, "respuesta[$i]")->textInput(['maxlength' => true])->label("<strong>" . $pregunta->descripcion . "</strong>") ?>
<?php endif; ?>
<?php if($pregunta->tipo == 2): ?>
<?= $form->field($model, "respuesta[$i]")->textarea(['maxlength' => true], ["style" => "resize: none;"])->label("<strong>" . $pregunta->descripcion . "</strong>") ?>
<?php endif; ?>
<?php if($pregunta->tipo == 3): ?>
<?= $form->field($model, "respuesta[$i]")->fileInput()->label("<strong>" . $pregunta->descripcion . "</strong>") ?>
<?php endif; ?>
<?php endforeach; ?>
<div class="form-group">
<?= Html::submitButton('Guardar', ['class' => 'btn btn-success']) ?>
</div>
<?php ActiveForm::end(); ?>
Model
public function rules()
{
return [
[['idpregunta', 'idinscripcion'], 'required'],
['respuesta', 'each', 'rule' => ['required']],
[['idpregunta', 'idinscripcion'], 'integer'],
[['respuesta'], 'string', 'max' => 500],
[['idpregunta'], 'exist', 'skipOnError' => true, 'targetClass' => Pregunta::className(), 'targetAttribute' => ['idpregunta' => 'id']],
[['idinscripcion'], 'exist', 'skipOnError' => true, 'targetClass' => Inscripcion::className(), 'targetAttribute' => ['idinscripcion' => 'idInscripcion']],
];
}
Controller
public function actionResponderFormulario($slug){
$evento = $this->findModel("", $slug);
$inscripcion = Inscripcion::find()->where(["idEvento" => $evento->idEvento, "idUsuario" => Yii::$app->user->identity->idUsuario])->one();
$preguntas = Pregunta::find()->where(["idevento" => $evento->idEvento])->all();
$model = new Pregunta();
if($inscripcion ==null){
$preguntas = Pregunta::find()->where(["idevento" => $evento->idEvento])->all();
return $this->render('responderFormulario',
["preguntas" => $preguntas,
"eventos" => $evento,
"model" => $model]);
}
}
The most important thing for me is for the questions to validate in general, I don’t care that much to validate each type separately, because I may remove this later.
Thank you in advance
I think this part of string validation with max length should change to this:
['respueusta', 'each', 'rule' => ['string', 'max' => 500]]
Sometime respuesta field receives the file in tipo=3 , you can not do this for clean and structured code. define another field for files.
I have two models in my Yii2 project. One model: ApartmentBuilding depends on another model: Caretaker via a relationship. Assuming I am creating an ApartmentModel via a form whereby I select the caretaker from a dynamic dropdown. I then realize that the caretaker I need is not added yet to the database, hence, I have to add the details of the caretaker from the ApartmentBuilding model form then proceed filling in the details of the ApartmentBulding form. That is the usecase of my problem.
So far, I have managed to launch the Caretaker model form via a modal from the ApartmentBulding form. On submitting the details of the Caretaker model form, the site redirects to the view of the CaretakerController. However, what I need is to be able to get the dropdown form refresh with the new details of the caretaker that I just added via a modal, and be able to proceed with the filling of the rest of the form.
Any help in cracking this will be appreciated.
Here are my codes so far:
ApartmentBuilding _form.php
<?php
use yii\helpers\Html;
use kartik\widgets\ActiveForm;
use app\models\Landlord;
use app\models\Caretaker;
use yii\helpers\ArrayHelper;
use yii\bootstrap\Button;
use yii\helpers\Url;
/* #var $this yii\web\View */
/* #var $model app\models\ApartmentBuilding */
/* #var $form yii\widgets\ActiveForm */
?>
<div class="apartment-building-form">
<?php $form = ActiveForm::begin([
'type' => ActiveForm::TYPE_HORIZONTAL,
'formConfig' => ['labelSpan' => 3, 'deviceSize' => ActiveForm::SIZE_TINY],
]); ?>
<?= $form->field($model, 'apartment_name')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'landlord_id')->dropDownList(ArrayHelper::map(Landlord::find()->select(['landlord_id', 'first_name', 'last_name'])->all(), 'landlord_id', 'displayName'),['class' => 'form-control inline-block', 'prompt'=>'Select Landlord']) ?>
<?= $form->field($model, 'physical_address')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'plot_number')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'address')->widget(\kalyabin\maplocation\SelectMapLocationWidget::className(), [
'attributeLatitude' => 'latitude',
'attributeLongitude' => 'longitude',
'googleMapApiKey' => 'YOUR_API_KEY_HERE',
]) ?>
<?= $form->field($model, 'number_of_floors')->textInput() ?>
<?= $form->field($model, 'apartment_desc')->textInput(['maxlength' => true]) ?>
<div class="form-group kv-fieldset-inline">
<?= Html::activeLabel($model, 'caretaker_id', ['label'=>'Caretaker', 'class'=>'col-sm-3 control-label']) ?>
<div class="col-sm-8">
<?= $form->field($model, 'caretaker_id',['showLabels'=>false])->dropDownList(ArrayHelper::map(Caretaker::find()->select(['caretaker_id', 'first_name', 'last_name'])->all(), 'caretaker_id', 'displayName'),['class' => 'form-control inline-block', 'prompt'=>'Select Caretaker']) ?>
</div>
<div class="col-sm-1">
<?= Html::button('<i class="glyphicon glyphicon-plus"></i>', ['value'=>Url::to(['caretaker/new']), 'title' => 'Create New Caretaker', 'class' => 'btn btn-success showModalButton']) ?>
</div>
</div>
<?= $form->field($model, 'other_apt_details')->textInput(['maxlength' => true]) ?>
<div class="col-sm-offset-3 col-sm-9">
<?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
<?= Html::resetButton('Reset', ['class' => 'btn btn-default']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
Caretaker _form.php
<?php
use yii\helpers\Html;
use kartik\widgets\ActiveForm;
use kartik\widgets\DatePicker;
/* #var $this yii\web\View */
/* #var $model app\models\Caretaker */
/* #var $form yii\widgets\ActiveForm */
?>
<div class="caretaker-form">
<?php $form = ActiveForm::begin([
'type' => ActiveForm::TYPE_HORIZONTAL,
'formConfig' => ['labelSpan' => 3, 'deviceSize' => ActiveForm::SIZE_TINY],
]); ?>
<?= $form->field($model, 'first_name')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'last_name')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'sex')->dropDownList(['Male' => 'Male', 'Female' => 'Female'],['prompt'=>'Select Sex']) ?>
<?= $form->field($model, 'date_of_birth')->widget(DatePicker::classname(), ['options' => ['placeholder' => 'Enter birth date ...'], 'pluginOptions' => ['autoclose'=>true, 'format' => 'yyyy-mm-dd']]) ?>
<?= $form->field($model, 'address')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'mobile')->widget(\yii\widgets\MaskedInput::className(), ['mask' => '254999999999',]) ?>
<div class="col-sm-offset-3 col-sm-9">
<?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
<?= Html::resetButton('Reset', ['class' => 'btn btn-default']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
CaretakerController actionNew()
public function actionNew()
{
$model = new Caretaker();
$model->company_id = Yii::$app->user->identity->company_id;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return false;
} else {
return $this->renderAjax('create', [
'model' => $model,
]);
}
}
Modal Handler:
<?php
yii\bootstrap\Modal::begin([
'headerOptions' => ['id' => 'modalHeader'],
'id' => 'modal',
'size' => 'modal-lg',
//keeps from closing modal with esc key or by clicking out of the modal.
// user must click cancel or X to close
'clientOptions' => ['backdrop' => 'static', 'keyboard' => FALSE]
]);
echo '<div id="modalContent"><div style="text-align:center"><?= Html::img("#web/img/loading.gif");?></div></div>';
yii\bootstrap\Modal::end();
?>
modal-popup.js
$(function(){
//get the click of modal button to create / update item
//we get the button by class not by ID because you can only have one id on a page and you can
//have multiple classes therefore you can have multiple open modal buttons on a page all with or without
//the same link.
//we use on so the dom element can be called again if they are nested, otherwise when we load the content once it kills the dom element and wont let you load another modal on click without a page refresh
$(document).on('click', '.showModalButton', function(){
//check if the modal is open. if it's open just reload content not whole modal
//also this allows you to nest buttons inside of modals to reload the content it is in
//the if else are intentionally separated instead of put into a function to get the
//button since it is using a class not an #id so there are many of them and we need
//to ensure we get the right button and content.
// if ($('#modal').data('bs.modal').isShown)
if ($("#modal").data('modal') && $("#modal").data('modal').isShown){
$('#modal').find('#modalContent')
.load($(this).attr('value'));
//dynamically set the header for the modal
document.getElementById('modalHeader').innerHTML = '<h4>' + $(this).attr('title') + '</h4>';
} else {
//if modal isn't open; open it and load content
$('#modal').modal('show')
.find('#modalContent')
.load($(this).attr('value'));
//dynamiclly set the header for the modal
document.getElementById('modalHeader').innerHTML = '<h4>' + $(this).attr('title') + '</h4>';
}
});
});
$(function(){
//load the current page with the conten indicated by 'value' attribute for a given button.
$(document).on('click', '.loadMainContent', function(){
$('#main-content').load($(this).attr('value'));
});
});
Note
The modal handler code and the modal-popup.js are reused by several other modals such as view and create forms. The code for the modal handler is in the main.php of the layouts folder.
I have managed to solve the problem by doing the following (More thoughts on the same still welcome, I know there could be more perfect way of doing this):
I added the following Javascript code to the caretaker _form.php
<?php
$this->registerJs("$('#createcaretaker').click(function() {
var firstName = $('#caretaker-first_name').val();
var lastName = $('#caretaker-last_name').val();
var sex = $('#caretaker-sex').val();
var dOB = $('#caretaker-date_of_birth').val();
var address = $('#caretaker-address').val();
var mobile = $('#caretaker-mobile').val();
$('#modal').modal('hide');
$.get('new?firstName='+firstName+'&lastName='+lastName+'&sex='+sex+'&dOB='+dOB+'&address='+address+'&mobile='+mobile, function(success){
$('.refreshcaretaker').html(success);
});
});");
?>
Then, the submit button on the same form, I changed it to:
<button id="createcaretaker" type="button" class="btn btn-success">Create</button>
Hence, the button id is able to trigger the Javascript code above to send data to the server side.
I then created a controller action in the ApartmentBuilding controller to handle the data entry as follows:
public function actionNew($firstName, $lastName, $sex, $dOB, $address, $mobile)
{
$model = new Caretaker();
$model->company_id = Yii::$app->user->identity->company_id;
if ($firstName != '0' && $lastName != '0' && $sex != '0' && $dOB != '0' && $address != '0' && $mobile != '0') {
$model->first_name = $firstName;
$model->last_name = $lastName;
$model->sex = $sex;
$model->date_of_birth = $dOB;
$model->address = $address;
$model->mobile = $mobile;
$model->save();
$caretakers = Caretaker::find()->all();
foreach ($caretakers as $caretaker) {
echo '<option value="'.$caretaker->caretaker_id.'">'.$caretaker->displayName.'</option>';
}
} else {
return $this->renderAjax('caretaker_create', [
'model' => $model,
]);
}
}
Having done that, I changed the dropdown entry of the parent form to be able to refresh its values via ajax upon submitting the modal form to create the caretaker model as follows:
<?= $form->field($model, '[{$i}]caretaker_id',['showLabels'=>false])->dropDownList(ArrayHelper::map(Caretaker::find()->select(['caretaker_id', 'first_name', 'last_name'])->all(), 'caretaker_id', 'displayName'),['class' => 'form-control inline-block refreshcaretaker', 'prompt'=>'Select Caretaker']) ?>
Note the refreshcaretaker class that is triggered by the Javascript code above when the modal is submitted.
The following link talks more about using modal forms in Yii2.
I want to use PJax in Yii Grid View, not with the associative filter that comes inside the Grid View, but with the Search Filter that's outside it, so it can filter the results inside.
Here is the source of the index file:
<div class="cars-index">
<h1><?= Html::encode($this->title) ?></h1>
<?php echo $this->render('_search', ['model' => $searchModel]); ?>
<p>
<?= Html::a('Create Cars', ['create'], ['class' => 'btn btn-success']) ?>
</p>
<?= GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
'id',
'name',
['attribute' => 'code',
'label' => 'Colour',
'format' => 'raw',
'value' => 'colour',
'contentOptions' => function ($model, $key, $index, $column){
return ['style' => ' text-align: center; width: 100px;color:white;background-color:#'. $model -> code];
},
'headerOptions' => ['style' => 'text-align: center;'],
],
'price',
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
Am I supposed to create an active form just for the part I want to filter? Or is there another way?
If You can't simply add the filter to you table like this
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
I suggest you use a proper action and a proper search function called by submit based on a specified active form
for action form eg:
<?php $form = ActiveForm::begin([
'id' => 'search-form',
'method' => 'post',
'action' => ['controller/yourAction']
]); ?>
in the controller
$model = new yourActiveForm();
if ($model->load(Yii::$app->request->post()) ) {
$dataProvider = $searchModel->search( [ 'yuorSearchModel'=> ['your_att1' => $model->yourValue1]]);
}
then your render
Conforming to yii2 doc
Pjax only deals with the content enclosed between its begin() and
end() calls, called the body content of the widget. By default, any
link click or form submission (for those forms with data-pjax
attribute) within the body content will trigger an AJAX request. In
responding to the AJAX request, Pjax will send the updated body
content (based on the AJAX request) to the client which will replace
the old content with the new one. The browser's URL will then be
updated using pushState. The whole process requires no reloading of
the layout or resources (js, css).
You may configure $linkSelector to specify which links should trigger
pjax, and configure $formSelector to specify which form submission may
trigger pjax.
You must add the
<?php Pjax::begin(); ?>
.... your active form
<?php Pjax::end(); ?>
and configure the proper $linkSelect and $formSelector
In your filter view:
<div id="myFilter">
<?php $form = ActiveForm::begin([
'id' => 'myFilterForm',
'method' => 'post',
'action' => [...],
]); ?>
...
</div>
And make sure you render filter between Pjax::begin and Pjax::end
Yet here comes the trick. If your server does not respond within default timeout, Pjax gets ignored and page reloaded, so make sure the timeout is big enough:
<?php Pjax::begin([
'id'=>'myGrid',
'timeout' => 10000, // <------------ THIS !!!!!!!
'formSelector' => '#myFilterForm'
]); ?>
<?= $this->render('myFilter', ['model' => $searchModel]); ?>
<?= GridView::widget([
...
]); ?>
<?php Pjax::end(); ?>
Also in your controller, you might want to "reset" the search model, so only data from the request used are attributes actually used by search:
public function actionSearch()
{
$searchModel = new MySearch();
if ($searchModel->load(Yii::$app->request->post())) {
$searchModel = new MySearch(); // "reset"
$dataProvider = $searchModel->search(Yii::$app->request->post());
} else {
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
}
return $this->render('search', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
Hope this helps. Cheers!
I am a Yii2 beginner. I have almost completed all of my grid-view except for pagination. I tried to use pjax but can't find a solution.
You have to set timeout for Pjax (default is 1000 ms). Sometimes it is not enough and plugin will reload the page completely.
<?php \yii\widgets\Pjax::begin(['timeout' => 10000, 'clientOptions' => ['container' => 'pjax-container']]); ?>
<?= GridView::widget([
// ... configuration here
]);?>
<?php \yii\widgets\Pjax::end(); ?>
see here
Put your code between Pjax::begin and Pjax::end this work for every thing not only the gridview
<?php \yii\widgets\Pjax::begin(); ?>
<?= GridView::widget([
// ... configuration here
]);?>
<?php \yii\widgets\Pjax::end(); ?>
This May Help...:)
Just start and end Pjax thats all..
<?php
use yii\widgets\Pjax;
<?php Pjax::begin(['id'=>'type_id']); //id is used for jquery opertaion ?>
<?php echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
//'id',
//'user_id',
'type',
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
<?php Pjax::end(); ?>
I'm building a mobile application with CakePHP 2.3 and jQuery Mobile.
I'm having a tough time making the login work.
I send out links that contain surveys, and if the user is not logged in on their mobile browser, it first makes them sign in.
The problem is that the first time, it just refreshes to a blank page. If you close the page and reopen the link, it works, but that sucks. I'd like it to just redirect correctly.
Here's the view for login:
<div class="users form">
<?= $this->Session->flash('auth'); ?>
<?= $this->Form->create('User'); ?>
<fieldset>
<?
echo $this->Form->input('username');
echo $this->Form->input('password');
?>
</fieldset>
<?
$options = array(
'label' => 'Login',
'rel' => 'external',
'data-ajax' => false
);
?>
<?= $this->Form->end($options); ?>
</div>
As you can see, I tried rel=external and data-ajax=false.
Putting the login on the same page somehow might solve the issue, but that seems like it defeats the purpose of using CakePHP in the first place.
Any ideas? I'm stumped.
That's not going to create an AJAX form in Cake.
You have to disable the default action of the form, and then submit the data via Javascript. You can use the built in JsHelper in Cake to do this.
To disable auto submitting of the form you need to pass this to the create method.
Form->create('User',array('default'=>false)); ?>
You can then use the serializeForm method of the JsHelper.
<?php
$data = $this->Js->get('#UserForm')->serializeForm(array('isForm' => true, 'inline' => true));
$this->Js->get('#UserForm')->event(
'submit',
$this->Js->request(
array('action' => 'save'),
array(
'update' => '#UserForm',
'data' => $data,
'async' => true,
'dataExpression'=>true,
'method' => 'POST'
)
)
);
?>
Note: The above requires that you place the output from JsHelper in the proper location of your layout.
You can now render the form like this.
<div class="users form">
<?= $this->Session->flash('auth'); ?>
<?= $this->Form->create('User',array('default'=>false)); ?>
<fieldset>
<?
echo $this->Form->input('username');
echo $this->Form->input('password');
?>
</fieldset>
<?
$options = array(
'label' => 'Login',
);
?>
<?= $this->Form->end($options); ?>
</div>
This off the top of my head. So it's not test.