Trying to figure out a decent approach here...
I have a form, where based on if a user selects a drop down in the form or not, it shows or hides a few other form fields. If the form fields are visible I want them to be required. If they are not visible, I do not want them to be required.
I'm trying to figure out an approach for handling this inside my model rules - I tried something like this in my model rules() function:
$requiredFields = 'cashAtClosing, offerPrice, closingDate, financingType,surveyDays,'.
'earnestMoney, escrowAgent, escrowAgentAddress, surveyProvider, surveyDays, titlePolicyPayment,'.
'titleObjectionDays, titleCompany, titleCompanyAddress, optionFee, optionDays, optionCredit';
if ($this->financingType == "THIRDPARTYFINANCE")
{
Yii::trace("Add Financing Type Rules");
$requiredFields .= ',creditApprovalRequired,creditApprovalDays,loan1Amount, loan1DueInFullYears, '.
'loan1InterestNotToExceed, loan1InterestNotToExceedYears, loan1OriginationNotToExceed';
}
else
{
$safeFields .= ',creditApprovalRequired,creditApprovalDays,loan1Amount, loan1DueInFullYears, '.
'loan1InterestNotToExceed, loan1InterestNotToExceedYears, loan1OriginationNotToExceed';
}
array_push($rulesArray, array($requiredFields, 'required'));
problem is that it seems that the rules function is called prior to the model being populated so in my example here $this->financingType is always empty so this code doesn't wok.
Whats a better approach here?
Thanks.
Try to add this code to beforeValidate() method of your model. It's should help you.
But create $rulesArray property in your model. Add new rules to this variable via $this->rulesArray in beforeValidate() method and use this variable in your rules() method.
Related
For example I have model form MySubmitForm:
use yii\base\Model;
class MySubmitForm extends Model{
public $value1;
public $value2;
public $value3;
public function rules(){
return [['value1', 'value2'],'required'];
}
}
Here I have two required parameters ($value1, $value2) and I want one of them to be required or I want to user got error only when both $value1 and $value2 would be empty.
Can I achieve this from this form model?
You can use Custom validation function inside your validation rules.
Possible options:
1) Select the most important relevant field and add error to it.
2) Select multiple important relevant fields and add the same error message to them (we can store and pass message in a separate variable before passing to keep code DRY).
3) We can use not existing attribute name for adding error, let's say all because attribute existence is not checked at that point.
public function rules()
{
return [['value1', 'value2'],'yourCustomValidationMethod'];
}
public function yourCustomValidationMethod()
{
// Perform custom validation here regardless of "name" attribute value and add error when needed
if ($this->value1=='' && $this->value2=='') {
//either use session flash
Yii::$app->session->setFlash('error','You need to provide one of the fields');
//or use model error without any specific field name
$this->addError('all', 'Your error message');
}
}
Note you can use either session flash or model to notify the error use which every you like
I have a model in Yii that contains an array of another model type. I am then trying to validate that no duplicate emails are filled out in a form, where you can fill out for n number of persons at the same time.
My current approach is to trigger a custom validation of the "outer" model that holds all the entrants, however, that model is not accessible in the view, only the array of entrants is, and if I then trigger the error on the "outer" model, it will not be displayed to the user. Therefore I would like to trigger it for the first entrant that violates the rule, but how do I go about doing that?
My code that attempts this, looks like this so far:
/*
* Custom validation rule to hinder the same e-mail being used twice.
*/
public function noRepeatingEmails($attribute, $params)
{
if (!isset($attribute)) return;
$emails = array();
foreach($this->$attribute as $user)
{
if (isset($user) && strlen(trim($user->email)) != 0)
{
$emailToAdd = strtolower(trim($user->email));
if (in_array($emailToAdd, $emails))
{
$this->addError($user, '<my error message>');
return;
}
else
{
$emails[] = $emailToAdd;
}
}
}
}
This only results in a code 500 error though:
Illegal offset type
I presume that is because it is looking for the property "user" in my model, rather than adding an error to "$user" object.
How do I best accomplish this?
I have a .NET background, so I am probably doing loads wrong here however.
If I understood correctly from your comment, you want to validate your model before saving it. For this purpose, CActiveRecord provides beforeSave() method. You need to put this method inside your model:
protected function beforeSave()
{
if(parent::beforeSave())
{
if(/* Your validation goes here*/)
return true;
else
return false
}
else
return false;
}
When the result of this method is true, save() method will be called. Otherwise save() method won't be called and therefore no record will be saved into your database.
I have a table with a field called vat_free. So my model was created with a property $vat_free. Its value can be 0 or 1.
I want my view to show No or Yes, instead of 0 or 1. I can do it creating a getter like getVatFree(), but it seems like a messy solution, because then I'll have two properties to the same field, even though it would serve different purposes.
So how can I use only the original property $vat_free? Couldn't I modify its getter?
Creating method
public function getVatFreeString(){
return $this->vat_free ? 'Yes':'No';
}
Is proper solution, it's not messy.
You could do like
$vat_free = YES or NO
but right before save this object you would override object class with beforeSave() method like following:
beforeSave(){
if($this->vat_free = YES){
$this->vat_free = 1
}else{
$this->vat_free = 0;
}
}
and override afterFind() to do the reverse(for beforeSave()) translate. But this is even messy and will not work if u do bulk save or retrieve.
I see 2 solutions.
Go with what you have said getVatFree(), this is whole purpose of OOP encapsulation.
Instead of making 1 or 0 in db, do Y or N values, you can use them in both places without problems.
In your model, create a new field that will be used for display purposes only.
class User extends CActiveRecord
{
public $displayVatFreeFlag;
public function rules() { ... }
public function afterFind()
{
$this->displayVatFreeFlag = ($this->vat_free ? 'Yes':'No');
}
}
Then, in your field, display the field as normal.
Vat free : <?php echo $model->displayVatFreeFlag; ?>
i want create register page in 3 step:
Step1: Basic Infomation (ext: example.site/register/step1)
Step2: Profile Infomation (ext: example.site/register/step2)
Step3: Done (ext: example.site/register/step3)
looke like:
Somebody can help me?
To Achieve something like this you have two options:
Do it without reloading the page using javascript to show and hide relevant fields. As you declare a different url for each of your gui-prototypes, I think this is not what you want.
You send the data from one page to the other using models and the POST-data. I will explain this way in detail now, as this seems to be your desired solution. Actually this can be done in a single action if you implement functions to check if there is enough data to render the next page (see the model below).
The following code is not tested, but should give you an idea of how it could be done. Lets start with the model.
The model contains the data of all pages. Working with only model across the two input-pages makes things way easier.
class Registration extends CFormModel {
public $firstName;
public $lastName;
//...
public $gender;
public $age;
//...
public function rules()
{
//add all your field-rules here...
}
//further functions like attributeLabels(), etc. as needed
/**
* Returns the fullname as needed on page two
*/
public function getFirstname()
{
return $this->firstName . ' ' . $this->lastName;
}
public function hasValidStepOneData()
{
//validate the fields you need to continue to step two and return a boolean value.
//yii provides this functionality via the validate-function having the abiliy to only
//only validate certain fields at once. If one of them fails return false, otherwise true.
//see here: http://www.yiiframework.com/doc/api/1.1/CModel#validate-detail
}
public function hasValidStepTwoData()
{
//same as above but with values needed for completing step 2
//return boolean
}
}
Now to the controller. According to your URLs fromabove it should be like this:
class RegistrationController extends CController {
public function actionRegSteps()
{
$model = new Registration();
//get the values if there are any
if (isset($_POST['Registration']) {
$model->attributes = $_POST['Registration'];
}
//decide what to do depending on the data available
if ($model->hasValidStepOneData() && $model->hasValidStepTwoData()) {
//all data needed is present...save the data and redirect to success-page
//TODO: save data here...
$model->unsetAttributes();
$this->render('success');
} else if ($model->hasValidStepOneData()) {
//step one is complete...show step2
$this->render('reg_step2', array('model'=>$model));
} else {
//model is still empty...show step 1
$this->render('reg_step1', array('model'=>$model));
}
}
}
The views are quite simple. You have to keep in mind though that you have to add hidden fields on page two to keep the data of step 1. step one only contains input-fields for firstname, lastname and email. Step 2 contains input-fields for password, gender and age and also hidden fields for firstname, lastname and email. The success-view contains no input-fields at all, as the registration-process is completed by then.
Other than this custom-solution, there is a wizard-extension which could come in handy for you. You can find it here: http://www.yiiframework.com/extension/wizard-behavior/
Moreover a similiar solution was already answered here: https://stackoverflow.com/a/3551704/3402681
I provided you the answer above to give you a general insight on how you can handle this. There is no "correct" solution for this. You have to find the one which suits you the best. Hope I could help!
I would like to filter some fields in my form with strtolower() function. Unfortunately I can't find any example of doing that.
How can I write such filter, that will lowercase the input, check the database if element exists and then decide wheter to add the record or not?
1) new project custom validator (we will use it like value filter here):
/lib/validator/MyProjectStringLowerCase.class.php
<?php
class MyProjectStringLowerCase extends sfValidatorBase
{
/**
* #see sfValidatorBase
*/
protected function doClean($value)
{
return strtolower($value);
}
}
2) bound it to field:
$this->setWidget('my_field_name', new sfWidgetFormInputText());
$this->validatorSchema['my_field_name'] = new MyProjectStringLowerCase();
If you have some validator on that field already, you can merge them into combined validators this way:
$this->validatorSchema['my_field_name'] = new sfValidatorAnd(array(
$this->validatorSchema['my_field_name'], // the original field validator
new MyProjectStringLowerCase(),
));
The combined validators order influent how value will flow trough them, so if you want to have value filtrated in second validation, set MyProjectStringLowerCase as the first one.
There are 2 differences between this approach and using post processing (like doSave() for instance):
the value here will be filtered after each send (and will show
filtered in displaying of form errors)
You can reuse it very cleanly and easily in other fields or forms in
your project
In your Form, you can override the doSave() method to do any manual interventions that you need to do that aren't completed by the form validation methods.
For example:
public function doSave($con = null) {
$employee = $this->getObject();
$values = $this->getValues();
// do your filter
$this->values['name'] = strtolower($values['name']);
parent::doSave($con);
}