In the active form, I have 3 textinput and one checkbox.
All the 3 textinputs have rules that says cannot be empty. What I want is if the checkbox is clicked, It will disable the rules and will save the empty record in the database.
here is screen shot of the active form..
You could define the rules that way (using when):
public function rules()
{
return [
['cancelled', 'boolean'],
['checkNumber', 'required'],
['payee', 'required', 'when' => function ($model) {return !$model->cancelled;}],
['particulars', 'required', 'when' => function ($model) {return !$model->cancelled;}],
];
}
You may want to add whenClient as well to let the browser check this before it submits the form.
You can do something like this:
$model = new SomeForm();
if ($model->load(Yii::$app->request->post())){
if ($model->checkbox == true) $model->scenario = 'checked';
}
// your model rules:
[['name', 'email', 'subject', 'body'], 'safe', 'on' => 'checked']
or alternatively You can do this:
if ($model->checkbox == true) $model->save(false); //this will disable any validation so be carefull
edit:
if You need cliend side validation switch, You have to use this:
[['name', 'email', 'subject', 'body'], 'required', 'when' => function ($model) {
return $model->cancelled == '0';
}, 'whenClient' => new JsExpression("function (attribute, value) { return $('#mailform-cancelled').val() == '0';}")]
Related
This is my code:
[
'attribute' => 'status',
'value' => function ($model) {
return Html::dropDownList('status', ['10' => 'Active', '20' => 'Deactive']);
},
],
I just want dropdown in status column. If record is active or deactive it will be selected.
You need to use 'format' => 'raw' for the column options and your definition for the dropDownList() is wrong you need to have the selection string as the second parameter and the dropdown options as the third parameter. Change your code to below:
[
'attribute' => 'status',
'format' => 'raw',
'value' => function ($model) {
return Html::dropDownList('status', $model->status, ['10' => 'Active', '20' => 'Deactive']);
},
],
EDIT
You didnt had in the initial requirements that you waned to update the status too when the drop down is changed. You can bind ajax call to the drop-down.
Add the following javascript on top of your view where you are initializing the GridView.
NOTE: Change the url:'controller/update-status?id'+id in the ajax call to the relative controller where you want to update the status for the row, but dont remove the id
$js = <<<JS
$(document).on('ready pjax:success',function(){
$(".switch-status").on('change',function(){
var data={};
data[$(this).attr("name")]=$(this).val();
var id=$(this).closest("tr").data('key');
$.ajax({
method:'post',
url:'/controller/update-status?id='+id,
data:data,
success:function(data){
if(!data.success){
alert(data.message);
}else{
alert("Status updated.");
}
},
error:function(jqXHR, textStatus, errorThrown ){
alert(errorThrown);
}
});
});
});
JS;
$this->registerJs($js, yii\web\View::POS_END);
Then inside your GridView column for status change the dropdown to the following
return Html::dropDownList(Html::getInputName($model, 'active'), $model->active, [10 => 'Active', 20 => 'Deactive'], ['class' => 'switch-status']);
And the go to your controller and add the action code for updating the status
Note: Change the Model in the first line $model = Model::findOne($id); name with the respective model you are using.
public function actionUpdateStatus($id) {
$model = Affiliate::findOne($id);
$app = Yii::$app;
$request = $app->request;
if($request->IsAjax && $request->isPost) {
Yii::$app->response->format = Response::FORMAT_JSON;
if($model->load($request->post()) && $model->save()) {
return ['success' => true];
} else {
return [
'success' => false,
'message' => implode('<br />', ArrayHelper::getColumn($model->errors, '0'))
];
}
}
}
Use content property to render HTML elements. For example:
[
'attribute' => 'status',
'content' => function ($model) {
return Html::dropDownList('status', $model->status, ['10' => 'Active', '20' => 'Deactive']);
},
],
I write two scenario in Yii2 comment model, when user logged on or is guest.
my rules is:
public function rules()
{
return [
[['user_id'], 'required', 'on' => self::SCENARIO_USER],
[['name', 'email'], 'required', 'on' => self::SCENARIO_GUEST],
[['post_id', 'body', 'date'], 'required'],
[['user_id', 'parent_id', 'post_id', 'status'], 'integer'],
[['body'], 'string'],
[['date'], 'safe'],
[['name', 'email', 'site'], 'string', 'max' => 256],
];
}
and senarios funtion :
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios[self::SCENARIO_GUEST] = ['name' , 'email'];
$scenarios[self::SCENARIO_USER] = ['user_id'];
return $scenarios;
}
I use it as follows:
$commentModel = Yii::$app->user->isGuest ? new Comment(['scenario' => Comment::SCENARIO_GUEST]) : new Comment(['scenario' => Comment::SCENARIO_USER]);
if guest view form, only name and email checked and user fill form, no field checked !
why other rules don't check? how to fix it?
Refer Yii2 Scenarios
The scenarios() method returns an array whose keys are the scenario names and values the corresponding active attributes. An active attribute can be massively assigned and is subject to validation.
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios[self::SCENARIO_GUEST] = ['name' , 'email', 'date', 'body', 'site', 'post_id'];
$scenarios[self::SCENARIO_USER] = ['user_id', 'date', 'body', 'site', 'post_id'];
return $scenarios;
}
If you use scenarios() you need to define all attributes allowed to assign in given scenario. So if you want to allow guest to edit also body and date you need something like:
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios[self::SCENARIO_GUEST] = ['name' , 'email', 'body', 'date'];
$scenarios[self::SCENARIO_USER] = ['user_id'];
return $scenarios;
}
If you have many "shared" attributes between scenarios, you probably should not override this method and define it in rules() only.
See more in scenarios documentation.
New to yii2 so im trying to figure out how to use the required rule but only when a certain field is checked, as the form is has a radio field which allows a new user to create an account as a personal account or as a business account.
I have used use_vat_number as an sample radio button.
public function rules(){
return [
['vat_number', 'required' , 'when' => use_vat_number = 1],//the idea
];
}
Refer Yii2 when and whenClient property
public function rules() {
return [
['vat_number', 'required', 'when' => function ($model) {
return $model->use_vat_number == 1;
}, 'whenClient' => "function (attribute, value) {
var opValue = $('input:radio[name=\\'Here Radio Button Name\\']:checked').val();
return opValue==1;
}",
],
];
}
You should use anonymous function to configure when property:
public function rules(){
return [
[
'vat_number', 'required' ,
'when' => function ($model) {
return $model->use_vat_number == 1;
},
]
];
}
I want to add Ajax validation to check the form before updating. In the view, added to the required field
['enableAjaxValidation' => true]
In the controller in the actionUpdate
if (Yii::$app->request->isAjax && $modelForm->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
if ($modelForm->validate()) {
$model->setAttributes($modelForm->getAttributes());
if ($model->save()) {
return $this->redirect([my way]);
}
if ($model->hasErrors()) {
return ActiveForm::validate($model);
} else {
return ['success' => 1, 'html' =>
$this->renderPartial('view', [my data];
}
} else {
return ActiveForm::validate($modelForm);
}
}
The problem is that the choice of any value in the field, which is "enableAjaxValidation" => true, immediately leads to the saving of the model (even without pressing the save button). How can this be avoided?
In controller try it like this:
$model = new ExampleForm;
// validate any AJAX requests fired off by the form
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
Add this before your if statement, and remove Yii::$app->request->isAjax from your if statement.
Add some thing like below in your form
$form = ActiveForm::begin([
'id' => 'example',
'action' => ['your_action'],
'validateOnSubmit' => true,
'enableAjaxValidation' => true,
])
Bug or Feature? If I change request data with beforeMarshal and there is a validation error, the request data will not be given back modified.
This question may be related to How to use Trim() before validation NotEmpty?.
Modifying Request Data Before Building Entities
If you need to modify request data before it is converted into entities, you can use the Model.beforeMarshal event. This event lets you manipulate the request data just before entities are created. Source: CakePHP 3 Documentation
According to the book I would expect the request data is always changed, no matter if there is a validation error or not.
Example or test case:
// /src/Model/Table/UsersTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
// Required for beforeMarshal event:
use Cake\Event\Event;
use ArrayObject;
// Required for Validation:
use Cake\Validation\Validator;
class UsersTable extends Table {
public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options) {
$data['firstname'] = trim($data['firstname']);
}
public function validationDefault(Validator $validator) {
$validator
->add('firstname', [
'minLength' => [ 'rule' => ['minLength', 2], 'message' => 'Too short.' ],
])
;
return $validator;
}
}
If I enter " d" (Space-d) the validation error is shown, but the space itself is not removed in the form. I would expact the form showing only "d" because the space is removed from the request data with the beforeMarshal event. So... bug or feature?
My solution would be to use the trim()-function in the controller instead of the beforeMarshal event:
// /src/Controller/UsersController.php
// ...
public function add() {
$user = $this->Users->newEntity();
if ($this->request->is('post')) {
// Use trim() here instead of beforeMarshal?
$this->request->data['firstname'] = trim($this->request->data['firstname']);
$user = $this->Users->patchEntity($user, $this->request->data );
if ( $this->Users->save($user) ) {
$this->Flash->succeed('Saved');
return $this->redirect(['controller' => 'Users', 'action' => 'index']);
} else {
$this->Flash->error('Error');
}
}
$this->set('user', $user);
}
This way the space will be removed even if there is a validation error. Or did I miss another function similar to beforeMarshal which is really modifying the request data?
The main purpose of beforeMarshal is to assist the users to pass the validation process when simple mistakes can be automatically resolved, or when data needs to be restructured so it can be put into the right columns.
The beforMarshal event is triggered just at the start of the validation process, one of the reasons is that beforeMarshal is allowed to change the validation rules and the saving options, such as the field whitelist. Validation is triggered just after this event is finished.
As documentation explains, if a field does not pass validation it will automatically removed from the data array and not be copied into the entity. This is to prevent having inconsistent data in the entity object.
More over, the data in beforeMarshal is a copy of the request. This is because it is important to preserve the original user input, as it may be used elsewhere.
If you need to trim your columns and display the result of the trimming to your user, I recommend doing it in the controller:
$this->request->data = array_map(function ($d) {
return is_string($d) ? trim($d) : $d;
}, $this->request->data);
Not work. This is my beforeMarshal :
public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options)
{
$schema = $this->schema();
foreach($schema->columns() as $idx => $field ) {
$sc = $schema->getColumn($field);
if (isset($data[$field]) && $data[$field] != null) {
if ($sc['type'] == 'date') {
$date = DateTime::createFromFormat('d/m/Y',$data[$field]);
if ($date)
$data[$field] = $date->format('Y-m-d');
}
if ($sc['type'] == 'datetime') {
debug($data[$field]);
$date = DateTime::createFromFormat('d/m/Y',$data[$field]);
if ($date)
$data[$field] = $date->format('Y-m-d H:i:s');
}
}
}
debug($data);
}
The date commission_approved_date is correctly modified in beforeMarshal:
/src/Model/Table/AccountsTable.php (line 265)
object(ArrayObject) {
_referer => 'http://localhost/gessin/Accounts/edit/ODc?filter=eyJBY2NvdW50cy51c2VyX2lkIjoiMTA4NSIsIjAiOiJNT05USChBY2NvdW50cy5jb21taXNzaW9uX2RhdGUpID4gMCIsIllFQVIoQWNjb3VudHMuY29tbWlzc2lvbl9kYXRlKSI6IjIwMjAifQ=='
description => 'Provvigione su attivazione prodotto vod002'
notes => 'asd'
totalpaid => '0'
commission_approved_date => '2020-02-23 18:34:22'
}
But the same date is not, after patchEntity:
/src/Controller/AccountsController.php (line 203)
object(App\Model\Entity\Account) {
'id' => (int) 87,
'identifier' => null,
'company_id' => null,
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2020-02-29 14:01:50.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\FrozenTime) {
'time' => '2020-02-29 18:30:24.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'notes' => 'asd',
'total' => null,
'totaltax' => null,
'invoice_id' => null,
'description' => 'Provvigione su attivazione prodotto vod002',
'in_out' => null,
'is_refund' => null,
'client_id' => null,
'contract_id' => (int) 32,
'totalpaid' => (float) 0,
'user_id' => (int) 1085,
'commission_date' => object(Cake\I18n\FrozenTime) {
'time' => '2020-02-04 00:00:00.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'commission_approved_date' => object(Cake\I18n\FrozenTime) {
'time' => '2028-08-12 00:00:00.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},