Yii2 ActiveForm Ajax Validation and submit - ajax

I have made an Active form with validation and ajax submit.
The form is created in view:
<?php $form = ActiveForm::begin([
'id' => 'login-form',
'layout' => 'horizontal',
'method' => 'post',
'enableClientValidation' => false,
'enableAjaxValidation' => true,
'validationUrl' => 'panel/validate',
'fieldConfig' => [
'options' => [
'tag' => false,
],
'template' =>'{input}<p class="help-block help-block-error ">{error}</p>'
],
]); ?>
The validation action:
public function actionValidate()
{
$model = new LoginForm();
$request = \Yii::$app->getRequest();
if ($request->isAjax && $request->isPost && $model->load(Yii::$app->request->post())) {
\Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
return $this->renderAjax('login', [
'model' => $model,
]);
}
When i leave the fields blank for example, or do not specify the captcha i can see the ajax response:
{"loginform-username":["Username cannot be blank."],"loginform-password":["Password cannot be blank."],"loginform-captcha":["Captcha cannot be blank."]}
However, those errors not getting shown under my form fields. The form fields are created like this:
<?= $form->field($model, 'username')->textInput()
If i turn off ajax validation, the erros are displayed. What can be wrong here?

I'm afraid there is no possible way to display error while turning off 'tag' = false in fieldConfig.
Even though it works for server-side validation, the main problem is how yii.activeForm.js updateInput() function for fields works. When ajax request is completed, the .js tries to find an outer tag (of field) with .field-<model>-<attribute> class selector and fetch error-div children. As long as there is no outer .field tag, no error message is append to form.
I'm not 100% sure about it, but this is my understanding from debugging yii.activeForm.js functionality.
Actually, here is the similar question in yii2/issues, where SilverFire explains that there is no way to achieve this.
ActiveForm fieldConfig options tag=>false will render class attribute without any tag

Related

Validate Select2 in Yii2 via AJAX

I have Yii2 application which uses the Kartik plugin to initialize Select2 dropdowns in forms.
I have one particular Select2 which uses AJAX call to get the data for the drop down options.
<?=
$form->field($model, 'court_house_id', ['enableAjaxValidation' => true, 'selectors' => ['input' => '#' . $id . "-court-house"],'template' => FormHelper::GenerateFieldTemplate([6])])
->widget(Select2::classname(), [
'options' => ['id' => $id . "-court-house", 'placeholder' => Yii::t('app', 'Search court house...')],
'hashVarLoadPosition' => \yii\web\View::POS_READY,
'pluginOptions' => [
'dropdownParent' => new JsExpression("$('#$modalWindowId')"),
'allowClear' => true,
'minimumInputLength' => 2,
'language' => [
'errorLoading' => new JsExpression("function () { return '" . Yii::t('app', 'Search...') . "'; }"),
],
'ajax' => [
'url' => app\components\UrlMaker::link('data/court-house-list'),
'dataType' => 'json',
'data' => new JsExpression('function(params) { return {q:params.term}; }')
],
'escapeMarkup' => new JsExpression('function (markup) { return markup; }'),
'templateResult' => new JsExpression('function(courthouse) { return courthouse.text; }'),
'templateSelection' => new JsExpression('function (courthouse) { return courthouse.text;}'),
]])
->label(Yii::t('app', 'Court House'), ['class' => FormHelper::GenerateLabelClassTemplate([3])]);
?>
Intentionally pasting all of the code, although most of it is irrelevant I would assume.
I have this loaded in multiple dynamically created forms thus all the strange ids and selectors. However, the form has different dropdown which controls whether some of the fields are shown (and required) or not. This particular field above is only shown in one of the scenarios which all the other variations of the form do not have it. So the model has the following validation:
[['court_house_id', 'staff'], 'required', 'on' => self::SCENARIO_ONE],
By the way staff is just a regular text field and everything works for it.
In order to change the scenario, I have the following line in the view with the form:
<?php $model->scenario = \app\models\MyModel::SCENARIO_ONE; ?>
The problem is that when I submit the form empty, the staff field gets marked in red as invalid but the court house is marked in green as valid although it is empty.
If I go into the model and remove the 'on' => self::SCENARIO_ONE part then everything works as expected - on empty submit the court house field also lights up in red but that would be a problem for the rest of my scenarios where this field is not needed.
Any ideas what might be causing the problem and how to resolve it?
Try to set the scenario in controller before calling save() method, for example
$model = new MyModel(['scenario' => MyModel::SCENARIO_ONE])

How to properly hydrate and extract Doctrine Entities from Zend Forms

I'm just starting out with Doctrine and was rewriting some code to use Doctrine entities in some Forms.
I have an Entity Business which has some 1:n relations with addresses, employees, emails etc. the Setup is really basic and working fine.
To add new Businesses i created a BusinessForm and Fieldsets for each of my entities. Here the constructor of the form:
public function __construct($scenario='create', $entityManager = null) {
parent::__construct('business_form');
$this->scenario = $scenario;
$this->entityManager = $entityManager;
$this->setAttribute('method', 'post');
$businessFieldset = new BusinessFieldset($this->entityManager);
$businessFieldset->setUseAsBaseFieldset(true);
$this->add($businessFieldset);
$hydrator = new DoctrineHydrator($this->entityManager, new Business());
$this->setHydrator($hydrator);
$this->addElements();
$this->addInputFilter();
}
addElements just adds a Submit and CSRF input.
And here the Controller action:
public function addAction(){
$form = new BusinessForm('create', $this->entityManager);
if ($this->getRequest()->isPost()) {
$data = $this->params()->fromPost();
$form->setData($data);
if($form->isValid()) {
// save Object
return $this->redirect()->toRoute('subcontractor', ['action'=>'index']);
}
}
return new ViewModel([
'form' => $form
]);
}
The form validates and i can get the Data from the form with $form->getData(). But i cant figure out how to get a populated Object from the form using the form's hydrator. When I use setObject(new Business()) at the start of the controller i get an error while $form->isValid() is running :
Zend\Hydrator\ArraySerializable::extract expects the provided object to implement getArrayCopy()
Isnt that the wrong hydrator being called ?
If i dont setObject() but instead use $form->bind(new Business()) after the validation i get an empty Object from $form->getObject(). If i get the data and hydrate a new Object with the form's hydrator like so : $form->getHydrator()->hydrate($data['business], new Business()) i do get the populated Object i was expecting. (Business being the name of the base fieldset)
So my question is, how to i get the result of the last call from just using $form->getObject() after the validation?
[EDIT]
The Problem seems to be with the Collections of Fieldsets used as sub-fieldsets in the businessfieldset. If i validate the form without using the collections i do get the expected Business Object from $form->getData()
Here an example how i add the collection (in the business fieldset):
$this->add([
'name' => 'emails',
'type' => 'Zend\Form\Element\Collection',
'attributes' => [
'id' => 'business_emails',
],
'options' => [
'label' => 'Emails',
'count' => 1,
'should_create_template' => true,
'template_placeholder' => '__index__',
'allow_add' => true,
'target_element' => [
'type' => 'LwsSubcontractor\Form\EmailFieldset',
],
'target_class' => 'LwsSubcontractor\Entity\Email'
],
]);
and here the EmailFieldset:
public function __construct() {
parent::__construct('email');
$this->setObject(new Email());
$this->addElements();
}
protected function addElements() {
$this->add([
'name' => 'email',
'type' => 'Zend\Form\Element\Email',
'attributes' => [
'placeholder' => 'E-Mail (z.B. email#muster-email.de)',
'class' => 'form-control',
'required' => true,
'size' => 50,
],
'options' => [
'label' => 'Email',
],
]);
}
}
If using the Collections i get the Error message from above. So after adding a hydrator to each Fieldset i was fine.
Although i was under the impression that setting the hydrator for the form would result in the used fieldsets to inherit that hydrator.Or was this because of using the fieldsets as collections and not directly ?
You have to add the hydrator to all your fieldsets, personally I use DoctrineModule\Stdlib\Hydrator\DoctrineObject for doctrine entities.
I would also look at using the init() method to initialize your forms and add elements then register and retrieve your form and fieldsets through the FormElementManager, $serviceLocator->get('FormElementManager')->get(yourFieldsetorForm::class). The form can than be injected into your controller.
I hope this helps.

Passing variable to the view from zend form using AJAX load

controller:
public function searchAction()
{
$form = new Application_Form_Search;
$k = $form->getValue('keyword');
$car = new Application_Model_Car();
$mapper = new Application_Model_CarMapper();
$this->view->cars = $mapper->search($keyword);
}
form
<?php
class Application_Form_Search extends Zend_Form {
public function init(){
$this->setMethod('post');
$this->addElement('text', 'keyword', array(
'required' => true,
'label' => 'Keyword:'
));
$this->addElement('button', 'submit', array(
'required' => false,
'ignore' => true,
'label' => 'Search'
));
}
}
?>
original view page
$(document).ready(function(){
$("#submit").click(function(){
$("#main").load('/cars/search');
});
});
Here I am trying to return the search view at the "id=main" section in the original view page by using .load()
In the search view, <?=$this->keyword?> is showing null
it seems like there's problem fetching the keyword field from the form
Ok, you need to set the second variable of .load to send the data. At present you aren't sending the data to the .load function
so it would be more like this. This is assuming that you have id of keyword
$("#main").load('/cars/search', {'keyword':$('input#keyword').val()});
or you could use
$("#main").load('/cars/search', {'keyword':$('[name="keyword"]').val()});
Please see
http://api.jquery.com/load/
Also remember to escape the value so people can't send malicious code through your form.
Hope that helps

Ajax is not working in yii

In my Yii web application, any type of Ajax call like Ajax validation, Ajax for dependent dropdown etc.... Is not working.
My codes are,
In my form page:
<?php
$form = $this->beginWidget('CActiveForm', array(
'id' => 'workdetails-form',
'enableClientValidation' => true,
'clientOptions' => array(
'validateOnChange' => true,
'validateOnSubmit' => true,
),
// Please note: When you enable ajax validation, make sure the corresponding
// controller action is handling ajax validation correctly.
// There is a call to performAjaxValidation() commented in generated controller code.
// See class documentation of CActiveForm for details on this.
'enableAjaxValidation' => true,
'htmlOptions' => array('enctype' => 'multipart/form-data'),
));
?>
in controller:
public function actionCreate() {
$model = new Workdetails;
// Uncomment the following line if AJAX validation is needed
$this->performAjaxValidation($model);
if (isset($_POST['Workdetails'])) {
$model->attributes = $_POST['Workdetails'];
if ($model->validate()) {
if ($model->save()) {
$this->redirect(array('create'));
}
}
}
$this->render('create', array(
'model' => $model,
));
}
For dependant dropdown:
<div class="form-group col-sm-6">
<?php echo $form->label($model, 'designationid', array('class' => 'req')); ?>
<?php
$designation = CHtml::listData(Designation::model()->findAll(), 'designationid', 'designation_name');
echo $form->dropDownList($model, 'designationid', $designation, array(
'class' => 'form-control',
'prompt' => 'Please Select',
'ajax' => array(
'type' => 'POST',
'url' => $this->createUrl('workdetails/Fetchemployee'), // here for a specific item, there should be different URL
'update' => '#' . CHtml::activeId($model, 'employeeid'), // here for a specific item, there should be different update
'data'=>array('designationid'=>'js:this.value'),
)));
?>
<?php echo $form->error($model, 'designationid', array('class' => 'school_val_error')); ?>
</div>
How to solve this...
Please help me..
Arya I had the same problem with Yii1 and i gave up using yii-ajax validation cause i could not find a way to fix it. First make sure you have initialize/ register Yii-js file these are
yiiactiveform and yii.js
If you don't have these files on your project, it means you have not registered them. To register the core JS file proceed with this config in your main.
'clientScript' => array(
'scriptMap' => array(
'jquery.js' => true,
'jquery.min.js' => true,
),
),
or if that doesn't work use this on your main view in the header section.
Yii::app()->clientScript->registerCoreScript('jquery');
You can also add it to your base controller which is at components/Controller.php
public function init() {
parent::init();
Yii::app()->clientScript->registerCoreScript('jquery');
}
On your view have this when creating your forms. It will help in placing the error messages. to your elements
<?php
$form = $this->beginWidget('CActiveForm', array(
'id' => 'patient-registration-form',
'enableClientValidation' => True,
'enableAjaxValidation' => FALSE,
'clientOptions' => array(
'validateOnSubmit' => true,
'afterValidate' => 'js:function(form, data, hasError) {
if(hasError) {
for(var i in data) $("#"+i).parent().addClass("has-error");
return false;
}
else {
form.children().removeClass("has-error");
return true;
}
}',
'afterValidateAttribute' => 'js:function(form, attribute, data, hasError) {
if(hasError) $("#"+attribute.id).parent().addClass("has-error");
else $("#"+attribute.id).parent().removeClass("has-error");
$("#"+attribute.id).parent().addClass("has-success");
}'
),
'htmlOptions' => array(
'class' => 'form-horizontal form-bordered form-row-stripped',
),
));
?>
alternatively use Yii2 it has fixed alot of stufff and if you are loading the current page with ajax you need to render the the whole page including the js file again. since when you use renderPartial it doesn't initalize the js files hence no js scripts will work, including validation.

cakephp passing selection to JS Helper

I want to get the selected schoolFilter form value and place in [SELECTED VALUE HERE].
$data = $this->Js->get('#SchoolFilter')->serializeForm(array('isForm' => true, 'inline' => true));
$this->Js->get('#SchoolSchoolId')->event(
'change', $this->Js->request(
array('action' => 'assign_school_ads/', [SELECTED VALUE HERE], array(
'update' => '#results',
'data' => $data,
'async' => true,
'dataExpression' => true,
'method' => 'POST'
)
)
);
// School Filter
$schoolFilter = $this->Form->create('School', array('id' => 'SchoolFilter');
$schoolFilter .= $this->Form->input('school_id', array('label'=>'Schools', 'empty'=>'- select -');
$schoolFilter .= $this->Form->end();
I have seen variations on this question but without any clear answer, except to just forget using JS Helper. Is it possible within the context of JS Helper? And if not, can I get the value using regular JQuery, then inject it into JS Helper.
Use following code :-
$this->Js->get('#SchoolFilter');
$this->Js->event('change', $this->Js->request(array('controller'=>'put-ur-controller-ame-here','action' => 'assign_school_ads'),array('async' => true,'update' => '#results','method' => 'post','dataExpression'=>true,'data'=> $this->Js->serializeForm(array('isForm' => true,'inline' => true)))));
the serialize() function sends the form data to the php action so we can see which option was selected and decide what to update in the ajax call.
the form data will be found in $this->data in the action (just like after a form has been submited).
Don't forget to add $this->Js->writeBuffer(); in your layout just before the body closing tag. Otherwise all the ajax code will not be added to your page.

Resources