Validate Select2 in Yii2 via AJAX - 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])

Related

Yii2 ActiveForm Ajax Validation and submit

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

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.

Yii2: Reusing Form Fields through Widget

I have a field that is a Select2 widget field and it's usually used in many forms, but copy pasting the same code after a while gets really annoying. Therefore I decided perhaps its best to create a widget just for this field.
The field is as follows
<?= $form->field($model, 'contact_id')->widget(Select2::className(), [
'initValueText' => empty($model->contact_id) ? '' : $model->contact->contact_id . ' ' . $model->contact->fullname,
'options' => [
'class' => 'input-sm',
'id' => 'contact_id',
'placeholder' => '-- Search --',
'disabled' => $disabled,
'onchange' => new JsExpression("get_contact_info($(this).val())"),
],
'pluginOptions' => [
'allowClear' => true,
'language' => [
'errorLoading' => new JsExpression("function () { return 'Waiting for results...'; }"),
],
'ajax' => [
'url' => $fetch_url,
'dataType' => 'json',
'data' => new JsExpression('function(params) { return {q:params.term}; }'),
'results' => new JsExpression('function(data,page) { return {results:data.results.text}; }'),
],
'escapeMarkup' => new JsExpression('function (markup) { return markup; }'),
'templateResult' => new JsExpression('function(contact) { return contact.text; }'),
'templateSelection' => new JsExpression('function (contact) { return contact.text; }'),
],
]); ?>
This field utilizes Ajax Fetching, and must allow to be used in create and update forms.
Can anyone please point me to the right direction.
I see two solution:
a) create widget - more work, but flexible using by adding additional settings
b) create separate view and render it - faster, but no so flexible

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.

Data validation with custom route issue (default url when errors occur)

I'm building a user panel, and having some problems with data validation. As an example, the page where you change your password (custom validation rule comparing string from two fields (password, confirm password)):
Route:
Router::connect('/profile/password', array('controller' => 'users', 'action' => 'profile_password'));
Controller:
function profile_password()
{
$this->User->setValidation('password'); // using the Multivalidatable behaviour
$this->User->id = $this->Session->read('Auth.User.id');
if (empty($this->data))
{
$this->data = $this->User->read();
} else {
$this->data['User']['password'] = $this->Auth->password($this->data['User']['password_change']);
if ($this->User->save($this->data))
{
$this->Session->setFlash('Edytowano hasło.', 'default', array('class' => 'success'));
$this->redirect(array('action' => 'profile'));
}
}
}
The problem is, that when I get to http://website.com/profile/password and mistype in one of the fields, the script goes back to http://website.com/users/profile_password/5 (5 being current logged users' id). When I type it correctly then it works, but I don't really want the address to change.
It seems that routes aren't supported by validation... (?) I'm using Cake 1.3 by the way.
Any help would be appreciated,
Paul
EDIT 1:
Changing the view from:
echo $form->create(
'User',
array(
'url' => array('controller' => 'users', 'action' => 'profile_password'),
'inputDefaults' => array('autocomplete' => 'off')
)
);
to:
echo $form->create(
'User',
array(
'url' => '/profile/password',
'inputDefaults' => array('autocomplete' => 'off')
)
);
does seem to do the trick, but that's not ideal.
Check the URL of the form in the profile_password.ctp view file.
Try the following code:
echo $this->Form->create('User', array('url' => array('controller' => 'users', 'action' => 'profile_password')));
Also, I think your form might be a little vulnerable. Try using Firebug or something similar to POST a data[User][id] to your action. If I'm right, you should be setting:
$this->data['User']['id'] = $this->Auth->user('id');
instead of:
$this->User->id = $this->Session->read('Auth.User.id');
because your id field is set in $this->data.
HTH.

Resources