I've Yii2 form containing form fields depending on action of page. Ex. Few fields appears when then action is create and few appears when action is update. I want to add required validation based on this scenario.
Ex.
<?= $form->field($model, 'unique_identifier')->textInput(['maxlength' => 45]) ?>
I am showing this field only when action => 'update'.
Now I want to add required validation for this and I tried this:
[['unique_identifier'], 'required', 'on' => 'update'],
But above validation not working. If I remove on=>update then its validating on both create and update scenario.
Any help would be appreciated.
ActiveRecord does not set scenario automaticaly when you update or create items. You must override update() method in your model and set scenario that you need. E.g. in your case
public function update($runValidation = true, $attributeNames = null)
{
$this->scenario = 'update';
return parent::update($runValidation, $attributeNames);
}
Also you can set scenario in your actionUpdate
public function actionUpdate($id)
{
$model = $this->findModel($id);
$model->scenario = 'update';
//load data from request, save model etc.
}
Related
I am currently trying to add a Clone action to my EmployeeCrudController.
The action should redirect to the Action::NEW view and have some prefilled values.
However I can't figure out how to prefill this form.
Here is where how I define my action within the EmployeeCrudController:
public function configureActions(Actions $actions): Actions
{
$cloneAction = Action::new('Clone', '')
->setIcon('fas fa-clone')
->linkToCrudAction('cloneAction');
return $actions->add(Crud::PAGE_INDEX, $cloneAction);
}
And this is how my cloneAction looks like, which currently redirects to the Action::NEW as expected but without prefilled values:
public function cloneAction(AdminContext $context): RedirectResponse
{
$id = $context->getRequest()->query->get('entityId');
$entity = $this->getDoctrine()->getRepository(Employee::class)->find($id);
$clone = new Employee();
$entity->copyProperties($clone);
$clone->setFirstname('');
$clone->setLastname('');
$clone->setEmail('');
$routeBuilder = $this->get(CrudUrlGenerator::class);
$url = $routeBuilder->build([
'Employee_lastname' => 'test',
'Employee[teamMembershipts][]' => $clone->getTeamMemberships(),
])
->setController(EmployeeCrudController::class)
->setAction(Action::NEW)
->generateUrl()
;
return $this->redirect($url);
}
You can set the value of a field in easyAdmin using the option data.
$builder->add('Employee_lastname', null, ['data' => $clone->getTeamMemberships()]);
If your field has multiple options, you can use the choices and choices_value.
I have a table called persons with id and name fields.
I have a create.php view that loads the model called Persons and now I want to add a checkbox called hasCar to show if a person has a car (so it is a boolean condition).
Then I have the send button that send the $model array of the form to the controller so I need to add the hasCar variable to $model array.
But the checkbox is not a column of the persons table so I got some errors because it is not part of the model.
I added the checkbox in this way but it is not working, of course.
<?= $form->field($model, 'hasCar')->checkbox(); ?>
Is it possible to send the hasCar variable inside the $model array? I mean, how can I send the hasCar variable to the controller when the send button is pressed?
Create a new model extending Person that contains hasCar member, and load the model from PersonForm class, such as:
class PersonForm extends Person
{
public $hasCar;
public function rules()
{
return array_merge(parent::rules(), [
[['hasCar'], 'safe'],
]);
}
public function attributeLabels()
{
return array_merge(parent::attributeLabels(), [
'hasCar' => 'Has car',
]);
}
}
You can't pass the variable to the $model object orbit is affiliated with a db table, you are right about this. You need to pass the variable to the controller via a request method (GET, POST).
Try :
Yii::$app->request->post()
for POST, and :
Yii::$app->request->get()
for GET.
Also on the form add the checkbox as an HTML class component.
EXAMPLE:
CONTROLLER:
...
$hasCar = Yii::$app->request->post('hasCar');
....
VIEW:
...
// We use ActiveFormJS here
$this->registerJs(
$('#my-form').on('beforeSubmit', function (e) {
if (typeof $('#hasCar-checkbox').prop('value') !== 'undefined') {
return false; // false to cancel submit
}
return true; // true to continue submit
});
$this::POS_READY,
'form-before-submit-handler'
);
...
<?= HTML::checkbox('hasCar', false, ['id' => 'hasCar-checkbox', 'class' => 'form-control']) ?>
...
More on ActiveFormJS:
enter link description here
I hope this answer covered you.
Damian
I have a number of fieldsets, and I would like to create an input filter class for each of them. The idea is then that for each of my forms, I can create an input filter class that is composed of other input filters. For instance, when creating an account via a registration form, I would like to take the base Account input filter I have for my Account entity and use it in a new input filter class that can modify the inputs or add additional ones. Something like the below.
class Register extends InputFilter
{
public function __construct(ObjectRepository $accountRepository, Account $accountFilter)
{
/***** Add inputs from input filters *****/
$this->inputs = $accountFilter->getInputs();
/***** Add additional validation rules *****/
// Username
$usernameAvailability = new NoObjectExists(array(
'object_repository' => $accountRepository,
'fields' => array('username'),
));
$username = $this->get('username');
$username->getValidatorChain()
->attach($usernameAvailability, true);
// E-mail
$emailAvailability = new NoObjectExists(array(
'object_repository' => $accountRepository,
'fields' => array('email'),
));
$email = $this->get('email');
$email->getValidatorChain()
->attach($emailAvailability, true);
}
}
I pass in an input filter to the constructor, and I want to add the inputs of this filter to my Register filter and modify the inputs.
The problem I am having is that only some of my inputs seem to validate as intended, and I cannot seem to figure out why. When I submit my form, only some inputs are validated as expected:
Interestingly, the e-mail input does not behave as expected when filling out an e-mail that already exists in my database. The result should be a validation error that it already exists, but this does not happen. If I debug and look at my form, I found the following:
The form's filter has the right inputs with the right validators, and as shown on the above image, the username input does seem to validate correctly. But for some reason, this is not visually reflected in my form.
Below is my code.
Fieldsets
class Profile extends Fieldset
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('profile');
$this->setHydrator(new DoctrineHydrator($objectManager))
->setObject(new ProfileEntity());
// Elements go here
$this->add(new AccountFieldset($objectManager));
}
}
class Account extends Fieldset
{
public function __construct()
{
parent::__construct('account');
$username = new Element\Text('username');
$username->setLabel('Username');
$password = new Element\Password('password');
$password->setLabel('Password');
$repeatPassword = new Element\Password('repeatPassword');
$repeatPassword->setLabel('Repeat password');
$email = new Element\Email('email');
$email->setLabel('E-mail address');
$birthdate = new Element\DateSelect('birthdate');
$birthdate->setLabel('Birth date');
$gender = new Element\Select('gender');
$gender->setLabel('Gender')
->setEmptyOption('Please choose')
->setValueOptions(array(
1 => 'Male',
2 => 'Female',
));
$this->add($username);
$this->add($password);
$this->add($repeatPassword);
$this->add($email);
$this->add($birthdate);
$this->add($gender);
$this->add(new CityFieldset());
}
}
Form
class Register extends Form
{
public function __construct()
{
parent::__construct('register');
// Terms and Conditions
$terms = new Element\Checkbox('terms');
$terms->setLabel('I accept the Terms and Conditions');
$terms->setCheckedValue('yes');
$terms->setUncheckedValue('');
$terms->setAttribute('id', $terms->getName());
// Submit button
$submit = new Element\Submit('btnRegister');
$submit->setValue('Register');
$profileFieldset = new ProfileFieldset($objectManager);
$profileFieldset->setUseAsBaseFieldset(true);
// Add elements to form
$this->add($terms);
$this->add($profileFieldset);
$this->add($submit);
}
}
View
$form->prepare();
echo $this->form()->openTag($form);
$profile = $form->get('profile');
$account = $profile->get('account');
echo $this->formRow($account->get('username'));
echo $this->formRow($account->get('password'));
echo $this->formRow($account->get('repeatPassword'));
echo $this->formRow($account->get('email'));
echo $this->formRow($account->get('birthdate'));
echo $this->formRow($account->get('gender'));
$city = $account->get('city');
echo $this->formRow($city->get('postalCode'));
echo $this->formRow($form->get('terms'));
echo $this->formSubmit($form->get('btnRegister'));
echo $this->form()->closeTag();
Controller
$form = new Form\Register();
$profile = new Profile();
if ($this->request->isPost()) {
$form->bind($profile);
$form->setData($this->request->getPost());
$form->setInputFilter($this->serviceLocator->get('Profile\Form\Filter\Register'));
if ($form->isValid()) {
// Do stuff
}
}
return new ViewModel(array('form' => $form));
Am I misunderstanding something here? Is there a better way to do this while still having multiple input filter classes? I would really prefer to keep my code maintainable like this rather than copying validation rules around for different forms. Sorry for the long post - it was really difficult to explain this problem!
Okay, it seems like I figured this out. Apparently my first approach was quite wrong. I found a way to have an input filter class for each of my fieldsets and then reuse these input filters for my form while adding additional validation rules for certain form elements (from my fieldsets). This way, I can have my generic validation rules defined in standard input filter classes per fieldset and modify them for different contexts (i.e. forms). Below is the code. The classes differ a bit from the question because that was slightly simplified.
Main input filter
// This input filter aggregates the "fieldset input filters" and adds additional validation rules
class Register extends InputFilter
{
public function __construct(ObjectRepository $accountRepository, InputFilter $profileFilter)
{
/***** ADD ADDITIONAL VALIDATION RULES *****/
// Username
$usernameAvailability = new NoObjectExists(array(
'object_repository' => $accountRepository,
'fields' => array('username'),
));
$emailInput = $profileFilter->get('account')->get('username');
$emailInput->getValidatorChain()->attach($usernameAvailability, true);
// E-mail
$emailAvailability = new NoObjectExists(array(
'object_repository' => $accountRepository,
'fields' => array('email'),
));
$emailInput = $profileFilter->get('account')->get('email');
$emailInput->getValidatorChain()->attach($emailAvailability, true);
/***** ADD FIELDSET INPUT FILTERS *****/
$this->add($profileFilter, 'profile');
}
}
Profile input filter
class Profile extends InputFilter
{
public function __construct(InputFilter $accountFilter)
{
$this->add($accountFilter, 'account');
// Add generic validation rules (inputs) for the profile fieldset here
}
}
The Account input filter referred to in the code above is a completely normal input filter class that extends Zend\InputFilter\InputFilter and adds inputs. Nothing special about it.
My fieldsets remain untouched and are identical to the ones in the question, as are the form class and the controller. The Register input filter is added to the form with the setInputFilter method, and that's it!
With this approach, each input filter instance is added to a fieldset - something that my first approach did not do. I hope this helps someone with similar problems!
I have a User entity with some validators specified in it.
But I want to extend that list of validators so as to add one based on a service call to check if a user email is not already used.
The problem is that I need an entity manager injected in the validator to call the service. And I can't (or rather don't know how to and don't want to :-) inject the entity manager into the entity.
I also want to leave in place the existing validation on the entity, as it is closer to the repository and might be used outside of my controller. So I cannot move all the validators out of this entity, into my new custom filter.
My new custom filter thus shall reuse the existing validators of the entity, as is.
The new custom filter is called UserFormFilter and it receives in its controller the existing entity filter, loops on it, adding to itself, each of the passed in validators:
class UserFormFilter extends InputFilter
{
public function __construct(EntityManager $em, $identity, InputFilter $userDefaultInputFilter)
{
// Add the validators specified in the user entity
foreach ($userDefaultInputFilter->inputs as $inputFilter) {
$this->add($inputFilter);
}
$this->add(array(
'name' => 'email',
'required' => true,
'filters' => array(),
'validators' => array(
array('name' => 'EmailAddress'),
array(
'name' => 'Application\Validator\User',
'options' => array('manager' => $em, 'identity' => $identity)
)
),
));
}
}
I can now instantiate a custom UserFormFilter in my UserController:
$formFilter = new \Application\Form\UserFormFilter($em, $user->getInputFilter());
$form->setInputFilter($formFilter);
You can see in the above code that the custom UserFormFilter takes the default filter specified in my User entity getInputFilter method. As a side note, it also passes the entity manager enabling the custom filter to carry out the service call.
I have the following code for updating a Yii model:
public function actionSettings($id) {
if (!isset($_POST['save_hostname']) && isset($_POST['Camera']) && isset($_POST['Camera']['hostname'])) {
$_POST['Camera']['hostname'] = '';
}
$model = $this->loadModel($id);
$model->setScenario('frontend');
$this->performAjaxValidation($model);
if (isset($_POST['Camera'])) {
$model->attributes = $_POST['Camera'];
unset($model->api_password);
if ($model->save()) {
Yii::app()->user->setFlash('success', "Camera settings has been saved!");
} else {
Yii::app()->user->setFlash('error', "Unable to save camera settings!");
}
}
$this->render('settings', array(
'model' => $model,
));
}
This works fine, except in my model I have code like this:
<h1>Settings For: <?php echo CHtml::encode($model->name); ?></h1>
The problem is that, even when the user input fails validation, the h1 tag is having bad input echoed out into it. If the input fails the validation, the h1 attribute should stay the same.
I can 'reset' the $model variable to what is in the database before the view is returned, but this then means I don't get any error feedback / validation failed messages.
Is my only option to have 2 $models ($model and $data perhaps), one used for handling the form and the other for sending data to the page? Or does someone have a more elegant solution?
performAjaxValidation assigns all save attributes to the model so this behavior is normal.
I would reload model if save fails.
$model->refresh();
I am sure I am going about this the wrong way, but I need to unset an array key from one of my choices in a sfWidgetFormChoice. The only way to get that variable to the Form is from the action. Here's what I have:
Action:
$id = $request->getParameter('id');
$deleteForm = new UserDeleteForm();
$choices = array();
$choices = $deleteForm->getWidgetSchema('user')->getAttribute('choices');
unset($choices[$id]); //I obviously don't want the user to be able to transfer to the user being deleted
$this->deleteForm = $deleteForm;
Form:
$users = Doctrine_Core::getTable('sfGuardUser')->getAllCorpUsers()->execute();
$names = array();
foreach($users as $userValue){
$names[$userValue->getId()] = $userValue->getProfile()->getFullName();
};
// unset($names[$id]); //this works, but I can't figure out how to get $id here.
$this->widgetSchema['user'] = new sfWidgetFormChoice(array(
'choices' => $names
));
$this->validatorSchema['user'] = new sfValidatorChoice(array(
'required' => true,
'choices' => $names
));
Understanding forms and actions:
Usually we will setup a form with fields, print it in a html page and fill the form with data. Pressing the submit form button will send all the data to a method defined in your form action html attribute.
The method will receive and get a $request , with a lot of parameters and also the form with the data. Those values will be processed in the action.
Lets look how it exactly works in symfony:
Define and Setup a symfony form, like the one you have shown above.
Print the form and in the action parameter point to the submit method
which will receive the request:
<form action="currentModuleName/update"
Symfony will automatically send the request to the action.class.php
of your module, and will look for and send the data to the function
executeUpdate
public function executeUpdate(sfWebRequest $request){ //...
$this->form = new TestForm($doctrine_record_found);
$this->processForm($request, $this->form); }
After some checks, symfony will process the form and set a result
template.
processForm(sfWebRequest $request, sfForm $form)
{ ... } $this->setTemplate('edit');
In the processForm of your module action.class.php, you should process all the received values (request) also with the form:
protected function processForm(sfWebRequest $request, sfForm $form)
{
$form->bind($request->getParameter($form->getName()), $request->getFiles($form->getName()));
if ($form->isValid())
{
$formValues = $this->form->getValues();
$Id = $formValues['yourWidgetName'];
}
}
You may check the following link for an example like yours, about how to process a sfWidgetFormChoice.
And now answering to the real question, in order to select the deleted users, add the following code in your action:
//process the form, bind and validate it, then get the values.
$formValues = form->getValues();
$choicesId = $formValues['choices'];
Pass variable from action to the form:
Excuse me if I have not understand your question at all but in case you need to pass some parameters from your action to the form, send the initialization variables in an array to the form constructor:
Pass a variable to a Symfony Form
In your case, get the list of users, delete the user you dont want and send the non deleted users to the form constructor.
You will need to redeclare/overwrite your form again in the configure() function so that you could change the initialization of the form. Copy and paste the same code into the configure() function and comment the line: //parent::setup();
class TbTestForm extends BaseTbTestForm
{
public function configure()
{
//.. copy here the code from BaseTbTestForm
//parent::setup();
$vusers = $this->getOption('array_nondeleted_users');
//now set the widget values with the updated user array.
}
}