I encountered a strange bug yesterday. I have a form where I enabled the enableAjaxValidation and give a specific validation url to validate a coupon code. The problem append when I blurred or submitted the form. If the field was empty, it was redirecting to the validation url instead of making an ajax request. If the field was filled, the validation worked perfectly.
Even weirder, while searching for the solution, I found that if I added a validation rule in the Model just before the one about the field causing problem, it fixes the issue. Whatever the validation or the attribute… Seems like a time bomb to me so I think to script the AJAX manually for this form.
My guess is there’s a sequence of validation rules that creates an exception in the generation of the active form javascript.
Here’s the code (very simple) :
_form.php
<?php $formCoupon = ActiveForm::begin([
'id' => 'transaction-coupon-form',
'enableAjaxValidation' => true,
'validationUrl' => ['transaction', 'action' => TransactionAction::ACTION_VALIDATE_COUPON],
'fieldConfig' => [
'template' => "{input}{hint}{error}",
],
]); ?>
<div class="row">
<div class="col-lg-6">
<?= $formCoupon->field($model, 'promoCode')->textInput(['maxlength' => true, 'placeholder' => 'Code Promo', 'disabled' => $validForm]) ?>
</div>
<div class="col-lg-4">
<?=SimpleLink::widget([
'type' => 'button',
'content_tag' => 'submitInput',
'label' => 'Appliquer',
])?>
</div>
</div>
<?php ActiveForm::end(); ?>
The validation action
public function validateCoupon()
{
$model = $this->controller->prepareTransaction();
$request = \Yii::$app->getRequest();
\Yii::$app->response->format = Response::FORMAT_JSON;
if ($request->isPost && $model->load($request->post())) {
return ActiveForm::validate($model, ['promoCode']);
}
return [];
}
Model validation rules
public function rules()
{
return array_merge([
[['public_user_id', 'coupon_id', 'status', 'is_deleted', 'is_free', 'created_by_admin', 'source_transaction_id'], 'integer'],
[['discount', 'subtotal', 'tps', 'tvq', 'total', 'order_number', 'secret_key', 'date_created', 'date_modified', 'billing_address', 'billing_first_name', 'billing_last_name', 'billing_email', 'billing_city', 'billing_postal_code'], 'required'],
[['discount', 'subtotal', 'tps', 'tvq', 'total'], 'number'],
[['date_created', 'date_modified', 'paid_date', 'recurring_end_date'], 'safe'],
[['order_number'], 'string', 'max' => 12],
[['billing_address'], 'string', 'max' => 100],
[['comments'], 'string', 'max' => 1000],
[['payment_method'], 'string', 'max' => 50],
[['promoCode'], 'string', 'max' => 20],
[['whateverAttribute'], 'string', 'max' => 20], // NEVER REMOVE - FIXES THE VALIDATION ISSUE
[['promoCode'], 'validateCoupon'],
[['billing_email'], 'email'],
[['secret_key', 'billing_address', 'billing_first_name', 'billing_last_name', 'billing_email', 'billing_city', 'billing_postal_code'], 'string', 'max' => 100],
[['coupon_id'], 'exist', 'skipOnError' => true, 'targetClass' => Coupon::className(), 'targetAttribute' => ['coupon_id' => 'id']],
[['public_user_id'], 'exist', 'skipOnError' => true, 'targetClass' => PublicUser::className(), 'targetAttribute' => ['public_user_id' => 'id']],
[['employer_id'], 'exist', 'skipOnError' => true, 'targetClass' => Employer::className(), 'targetAttribute' => ['employer_id' => 'id']],
[['created_by_admin'], 'exist', 'skipOnError' => true, 'targetClass' => AdminUser::className(), 'targetAttribute' => ['created_by_admin' => 'id']],
[['source_transaction_id'], 'exist', 'skipOnError' => true, 'targetClass' => Transaction::className(), 'targetAttribute' => ['source_transaction_id' => 'id']],
//[['acceptTerms'], 'required', 'requiredValue' => 1, 'message' => 'Vous devez accepter les conditions générales de ventes.'],
], $this->rules);
}
Validation code for the rule validateCoupon
public function validateCoupon($attribute, $params, $validator)
{
$coupons = Coupon::find()->andWhere(['code' => $this->$attribute])
->andWhere(['<=', 'start_date', date('Y-m-d')])
->andWhere(['>=', 'end_date', date('Y-m-d')])
->all();
if(!$coupons) {
Yii::$app->transaction->removeCoupon();
$this->addError($attribute, 'Le coupon n\'est pas valide.');
} else {
foreach($coupons as $coupon)
{
if($coupon->count && $coupon->getNumberOfUses() >= $coupon->count)
{
$this->addError($attribute, 'Le coupon n\'est plus disponible.');
continue;
}
// Add the coupon
Yii::$app->transaction->addCoupon($coupon->id);
break;
}
}
}