In my scenario I have a rest controller that I am validating input data.
I have built a form that I am using purely for custom validation that looks like this:
$validator
->requirePresence('sport_type')
->add('sport_type', 'Require fields missing', [
'rule' => function ($value) {
switch ($value) {
case 'Football':
$this->validator()
->requirePresence('football_id');
break;
case 'Basketball':
$this->validator()
->requirePresence('basketball_id');
break;
default:
return true;
}
return true;
}
])
->notEmpty('football_id')
->notEmpty('basketball_id')
return $validator;
What I need to be able to do, is in the default case, ensure that the other fields are null - if for example, sport_type = 'Motorsport', then the validator must return false if the input data DOES contain something in football_id or basketball_id.
I cannot see any kind of requireEmpty type of method in cake, so can anyone suggest how to accomplish this. Do I need a separate custom validator for that single rule, and how would I call it from this form validator ?
Related
I have 2 forms, in which the validation for a field in the second form is based on the value of a field in the first form. This works as expected when filling in the form top-down. However, when I change the value in the first form, the values object isn't updated in the validation.
My validate-function looks something like this:
const validate = values => {
const errors = {}
if (!values.username) {
errors.username = 'Required'
} else if (values.username.length < values.previous_field.length) {
errors.username = 'Your name can't be shorter than the previous field';
}
return errors;
}
When I change the previous field to a short value after filling in a valid username, the username-field never invalidates.
I decided to rename the forms so then would both have the same name. In this way the values do get updated, and you can use the validation in the way I would like.
Both forms are now named userdata, and the first one has all the validation logic:
#reduxForm({
form: 'userdata',
validate,
asyncValidate,
asyncBlurFields: ['car_licenceplate', 'user_zipcode']
})
Im currently making a Yii2 RESTful system with AngularJs.
In my database i've got several columns that i want to be able to return when doing a particular call from a certain point in my system.
The problem i'm having is how do i return only a handful of fields eg(id, title and stub) from the restful call in another part of my system so that it ignores other fields in the table.
I would ideally like it to work in a similar way to how a Models rules work with scenarios in yii.
There are two methods, I think:
1. use params
// returns all fields as declared in fields()
http://localhost/users
// only returns field id and email, provided they are declared in fields()
http://localhost/users?fields=id,email
// returns all fields in fields() and field profile if it is in extraFields()
http://localhost/users?expand=profile
// only returns field id, email and profile, provided they are in fields() and extraFields()
http://localhost/users?fields=id,email&expand=profile
2. overriding model's fields()
// explicitly list every field, best used when you want to make sure the changes
// in your DB table or model attributes do not cause your field changes (to keep API backward compatibility).
public function fields()
{
return [
// field name is the same as the attribute name
'id',
// field name is "email", the corresponding attribute name is "email_address"
'email' => 'email_address',
// field name is "name", its value is defined by a PHP callback
'name' => function () {
return $this->first_name . ' ' . $this->last_name;
},
];
}
// filter out some fields, best used when you want to inherit the parent implementation
// and blacklist some sensitive fields.
public function fields()
{
$fields = parent::fields();
// remove fields that contain sensitive information
unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);
return $fields;
}
more detail, refer to https://github.com/yiisoft/yii2/blob/master/docs/guide/rest-resources.md
You may use scenarios method inside your model for this, but you will have to extend a bit toArray method in order to make it work properly:
public function scenarios()
{
return array_merge(parent::scenarios(), [
'simple_info' => [
'email',
'name',
],
'login' => [
'id',
'email',
'name',
'auth_token',
],
]);
}
public function toArray(array $fields = array(), array $expand = array(), $recursive = true)
{
$scenarios = $this->scenarios();
$scenario = $this->getScenario();
if (!empty($scenarios[$scenario])) {
$data = parent::toArray($fields, $expand, $recursive);
return array_intersect_key($data, array_flip($scenarios[$scenario]));
}
return parent::toArray($fields, $expand, $recursive);
}
After this you may simply do something like this:
$model = new LoginForm();
if ($model->load(Yii::$app->request->post(), '') && $model->login()) {
$user = $model->getUser();
// Lets change scenario to login in order to get `auth_token` for authorization
$user->setScenario('login');
$user->generateAuthKey();
$user->save(FALSE);
return $user;
} else {
return $model;
}
As a side note (expanding on the answer from #Ganiks), if you are manually returning the list of Models, you will need to return them as a DataProvider (rather than simply as an array of Models) for the fields parameter to have an effect.
For example, if you do something like this...
class UserController extends yii\rest\Controller
{
public function actionIndex()
{
return User::find()->all(); // Not what you want
}
// ...
}
... then the fields parameter will not have the desired effect. However, if you instead do this...
class UserController extends yii\rest\Controller
{
public function actionIndex()
{
return new ActiveDataProvider([
'query' => User::find(),
'pagination' => false,
]);
}
// ...
}
... then the returned fields will only be those you specified in the fields parameter.
I am using a custom validation rule in CakePHP to be sure a value is entered into a field when a corresponding checkbox is marked:
Here's the validation rule within my model's validation array...
'tv_price'=>array(
'check'=>array(
'rule'=>array('check_for_tv_price'),
'message'=>'Please enter the television pricing information.',
),
)
...and here's my really simple custom validation function:
public function check_for_tv_price($check) {
if($this->data['Client']['tv']==1&&$this->data['Client']['tv_price']=="") {
return false;
}
if($this->data['Client']['tv']==1&&$this->data['Client']['tv_price']!="") {
return true;
}
if($this->data['Client']['tv']==0) {
return true;
}
}
I've tried adding 'required'=>false and 'allowEmpty'=>true at different points in the validation array for my tv_price field, but they always override my custom rule! As a result, a user can not submit the form because the browser prevents it (due to the required attribute).
For reference, the browser spits out the following HTML:
<input id="ClientTvPrice" type="text" required="required" maxlength="255" minyear="2013" maxyear="2018" name="data[Client][tv_price]"></input>
(Note the minyear and maxyear attributes are from the form defaults.)
Has anyone found a way to prevent the automatic insertion of the required attribute when using custom validation rules?
Any guidance would be much appreciated.
Thanks!
Chris
Set required to false and allowEmpty to true, that should do it for you.
'tv_price'=>array(
'check'=>array(
'rule'=>array('check_for_tv_price'),
'message'=>'Please enter the television pricing information.',
'required' => false,
'allowEmpty' => true
),
)
Hope this helps.
These seems like 2.3's new HTML5 magic stuff.
Try adding 'formnovalidate' => true to the $this->FormHelper->input() options in the view.
ref:
http://book.cakephp.org/2.0/en/appendices/2-3-migration-guide.html#formhelper
http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::input
Thank you for the input!
Upon further investigation, I actually do want the required attribute turned on when the form is checked; to achieve this I wrote two simple JavaScript functions (and used a third that I found online) to check the current status of the checkboxes and mark the field as required when appropriate.
In the view file, I added the function calls in two locations. The first block is in the onload function and is called when the window is loaded:
<script type="text/javascript">
window.onload = function() {
toggle_setup(document.getElementById('ClientTv'), 'tvPrice', false)
toggle_required(document.getElementById('ClientTv'), "ClientTvPrice")
}
</script>
These are a mess, I know. But they worked!
Note the fields that appear when the checkbox is clicked are enclosed in a div with a name that is used in the toggle_setup function to show or hide the div as needed. In this case, I named the div 'tvPrice'
<?php echo $this->Form->input('Client.tv', array('label'=>'Purchasing TV? <small>(Check if Yes)</small>', 'onclick'=>'toggle_setup(this, "tvPrice", "TV pricing");toggle_required(this, "ClientTvPrice")'));
echo $this->Form->input('Client.tv_price', array('label'=>'Enter TV Price','div'=>array('class'=>'hidden', 'id'=>'tvPrice')));
?>
And here are the JavaScript functions themselves:
function toggle_required(element, id) {
var object = document.getElementById(id)
if(element.checked) {
object.setAttribute( 'required', "required")
} else {
object.removeAttribute('required')
}
}
function toggle_setup(element, div, human_term) {
if(element.checked) {
document.getElementById(div).style.display='block'
} else {
if(!human_term) {
clearChildren(document.getElementById(''+div))
document.getElementById(div).style.display='none'
} else {
if(confirm('Are you sure you want to clear the client\'s '+human_term+' settings?')) {
clearChildren(document.getElementById(div))
document.getElementById(div).style.display='none'
} else {
element.checked = true
}
}
}
}
function clearChildren(element) {
for (var i = 0; i < element.childNodes.length; i++) {
var e = element.childNodes[i];
if (e.tagName) switch (e.tagName.toLowerCase()) {
case 'input':
switch (e.type) {
case "radio":
case "checkbox": e.checked = false; break;
case "button":
case "submit":
case "image": break;
default: e.value = ''; break;
}
break;
case 'select': e.selectedIndex = 0; break;
case 'textarea': e.innerHTML = ''; e.value=""; break;
default: clearChildren(e);
}
}
}
The above code uses a CSS class called "hidden" which I've defined as below:
.hidden {
display: none;
}
Per the above suggestions, I've added the 'required'=>false and 'allowEmpty'=>true lines to my validation array and it appears to be working.
Thank you for all the help!
I've a probllem with email textfield on which I want to perform multi validations. In detail:
1. Classic format email validation
2. Unique email check
Can I override email VType? or I have to create a custom VType? How can I perform two validation with two different error messages in a single VType?
Thanks
Regards
You can override the default validation using the validator attribute. For example, if you wish to enforce the standard rules and some other rules (e.g. defined by isSomeOtherRules() that returns a boolean), set the following attribute:
validator: function(value) {
return Ext.form.VTypes.email(value) && isSomeOtherRules(value);
}
Expanding on Andrew's post; we can return validation messages (shown below) to get the same look and feel of vtype error alert:
validator: function(value) {
if (!Ext.form.VTypes.cfpValidatePdf(value)) {
return 'File must be pdf';
} else if (!Ext.form.VTypes.cfpValidateFileNameSize(value)) {
return 'The maximum length of the filename is 64';
} else {
return true;
}
}
I need to validate a form with a bunch of inputs in it. And, if an input is invalid, indicate visually in the form that a particular attribute is invalid. For this I need to validate each form element individually.
I have one model & one view representing the entire form. Now when I update an attribute:
this.model.set('name', this.$name.val())
the validate method on the model will be called.
But, in that method I am validating all the attributes, so when setting the attribute above, all others are also validated, and if any one is invalid, an error is returned. This means that even if my 'name' attribute is valid, I get errors for others.
So, how do I validate just one attribute?
I think that it is not possible to just validate one attribute via the validate() method. One solution is to not use the validate method, and instead validate every attribute on 'change' event. But then this would make a lot of change handlers. Is it the correct approach? What else can I do?
I also think that this points to a bigger issue in backbone:
Whenever you use model.set() to set an attribute on the model, your validation method is run and all attributes are validated. This seems counterintuitive as you just want that single attribute to be validated.
Validate is used to keep your model in a valid state, it won't let you set an invalid value unless you pass a silent:true option.
You could either set all your attributes in one go:
var M=Backbone.Model.extend({
defaults:{
name:"",
count:0
},
validate: function(attrs) {
var invalid=[];
if (attrs.name==="") invalid.push("name");
if (attrs.count===0) invalid.push("count");
if (invalid.length>0) return invalid;
}
});
var obj=new M();
obj.on("error",function(model,err) {
console.log(err);
});
obj.set({
name:"name",
count:1
});
or validate them one by one before setting them
var M=Backbone.Model.extend({
defaults:{
name:"",
count:0
},
validate: function(attrs) {
var invalid=[];
if ( (_.has(attrs,"name"))&&(attrs.name==="") )
invalid.push("name");
if ( (_.has(attrs,"count"))&&(attrs.count===0) )
invalid.push("count");
if (invalid.length>0) return invalid;
}
});
var obj=new M();
obj.on("error",function(model,err) {
console.log(err);
});
if (!obj.validate({name:"name"}))
obj.set({name:"name"},{silent:true});
I recently created a small Backbone.js plugin, Backbone.validateAll, that will allow you to validate only the Model attributes that are currently being saved/set by passing a validateAll option.
https://github.com/gfranko/Backbone.validateAll
That is not the issue of Backbone, it doesn't force you to write validation in some way. There is no point in validation of all attributes persisted in the model, cause normally your model doesn't contain invalid attributes, cause set() doesn't change the model if validation fails, unless you pass silent option, but that is another story. However if you choose this way, validation just always pass for not changed attributes because of the point mentioned above.
You may freely choose another way: validate only attributes that are to be set (passed as an argument to validate()).
You can also overload your model's set function with your own custom function to pass silent: true to avoid triggering validation.
set: function (key, value, options) {
options || (options = {});
options = _.extend(options, { silent: true });
return Backbone.Model.prototype.set.call(this, key, value, options);
}
This basically passes {silent:true} in options and calls the Backbone.Model set function with {silent: true}.
In this way, you won't have to pass {silent: true} as options everywhere, where you call
this.model.set('propertyName',val, {silent:true})
For validations you can also use the Backbone.Validation plugin
https://github.com/thedersen/backbone.validation
I had to make a modification to the backbone.validation.js file, but it made this task much easier for me. I added the snippet below to the validate function.
validate: function(attrs, setOptions){
var model = this,
opt = _.extend({}, options, setOptions);
if(!attrs){
return model.validate.call(model, _.extend(getValidatedAttrs(model), model.toJSON()));
}
///////////BEGIN NEW CODE SNIPPET/////////////
if (typeof attrs === 'string') {
var attrHolder = attrs;
attrs = [];
attrs[attrHolder] = model.get(attrHolder);
}
///////////END NEW CODE SNIPPET///////////////
var result = validateObject(view, model, model.validation, attrs, opt);
model._isValid = result.isValid;
_.defer(function() {
model.trigger('validated', model._isValid, model, result.invalidAttrs);
model.trigger('validated:' + (model._isValid ? 'valid' : 'invalid'), model, result.invalidAttrs);
});
if (!opt.forceUpdate && result.errorMessages.length > 0) {
return result.errorMessages;
}
}
I could then call validation on a single attribute like so
this.model.set(attributeName, attributeValue, { silent: true });
this.model.validate(attributeName);