knockout validation inner observable validation is not triggred - javascript

I have the following JS object:
var form= function () {
var self = this;
self.Name = ko.observable().extend({
required: {
message: 'Name is required.'
}
});
self.Manager = ko.observable().extend({
required: {
message: 'Manager is required.'
}
});
self.Email = ko.observable().extend({
required: {
message: 'Email is required.'
},
email: {
message: 'Entered Email is not valid.'
}
});
self.Players = ko.observableArray([]);
}
Players array in the above JS object is populated using the following JS object using the new Player() syntax:
var Player = function () {
var self = this;
self.FirstName= ko.observable().extend({
required: {
message: 'Player First Nameis required.'
}
});
self.LastName= ko.observable().extend({
required: {
message: 'Player Last Name is required.'
}
});
}
And I have used the following settings for knockout-validation library:
ko.validation.init({
registerExtenders: true,
messagesOnModified: true,
insertMessages: false,
parseInputAttributes: true,
messageTemplate: null,
decorateInputElement: true,
errorElementClass: 'error',
grouping: {
deep: true,
observable: true
}
}, true);
I am using the viewModel.errors.showAllMessages() to display all the error messages in the view model. The problem I am facing is that the validations for the player object are not getting triggered as it is present inside the Form object. I have even set the deep property to true but even that is not working, what could be the issue?

The short answer is you will need to use { live: true } to track errors from objects that are added to observable arrays.
Longer answer:
I've used Jeroen's fiddle as a starting point and assumed that what you want is to show error messages for all validatable observables of all the players.
Use ko.validation.group(vm, options) to get the list of errors. It is the returned object (a computed observable returning an array of errors) that exposes showAllMessages function.
By default it will not perform a deep traversal of the supplied view model, so you will only get errors for Name, Manager, and Email properties (as I assume you are getting currently).
To make it use deep traversal you can supply { deep: true } option. That should give you errors for all players that were in the array at the time group function is called.
If you want it to also track objects added to observable arrays (players in your case) after group function is called, you can use { live: true } option. It will create a subscription on the array and update the list of errors each time the array is modified. Here is the issue that added it: https://github.com/Knockout-Contrib/Knockout-Validation/pull/223
Below is the relevant snipped and here is the fiddle:
https://jsfiddle.net/2a7t72be/1/
var form = function () {
var self = this;
self.showErrors = function(){
self.errors.showAllMessages();
};
// ...
self.Players = ko.observableArray([]);
self.errors = ko.validation.group(self, { deep: true, live: true });
}
PS. You can of course configure grouping options when calling validation.init (just do it before your call to group).

Related

Skip validation of model attribute using Backbone.Validation

I have a view that is rendered dynamically. It may have some inputs or may not have.
After a user fills everything and tries to send data I call this.model.isValid(true) ( or this.model.isValid() ) and it returns false even if the data from inputs is valid.
I think the cause is Backbone Validation tries to validate attributes of inputs we did not render.
Is there any solution to skip model attributes if we have no sticked elements of a view?
UPDATE:
My model is similar to this:
MyApp.module("RecordModel", function (RecordModel, MyApp, Backbone) {
RecordModel.recordModel = Backbone.Model.extend({
validation: {
inn: {
pattern: 'inn',
msg: MyApp.messages.inn
},
bik: {
pattern: 'bik',
msg: MyApp.messages.bik
},
uin: {
pattern: 'uin',
msg: MyApp.messages.uin
},
sum: {
pattern: 'sum',
msg: MyApp.messages.sum
}
}
});
});
Bindings:
bindings: {
'#uin': {
observe: 'uin',
setOptions: {
validate: true
},
events: MyApp.Validation.events.inputEvents
},
'#bik': {
observe: 'bik',
setOptions: {
validate: true
},
events: MyApp.Validation.events.inputEvents
},
'#inn': {
observe: 'inn',
setOptions: {
validate: true
},
events: ParkingMate.Validation.events.inputEvents
},
'#sum': {
observe: 'sum',
setOptions: {
validate: true
},
events: MyApp.Validation.events.inputEvents
}
}
So for some reason we din't render #sum input for instance. As we haven't it got in our DOM, it doesn't exists in RecordModel, but backbone still tries to validate it. Or if we have this input in our DOM, everything works fine.
How can I allow empty values but still validate if the user enters something?
By default, if you configure a validator for an attribute, it is
considered required. However, if you want to allow empty values and
still validate when something is entered, add required: false in
addition to other validators.
validation: {
value: {
min: 1,
required: false
}
}
If you can't let empty values (like at creation), Backbone.validation overrides isValid adding features to the default behavior. What's interesting is the array parameter we can pass:
// Check if name and age are valid
var isValid = model.isValid(['name', 'age']);
With this, we can then validate only the fields that exist within the model at the moment:
model.isValid(_.keys(model.attributes));

How to add a complex object to model of each angular-formy type when validated?

Say I have a custom angular-formly type that extends a input type.
Lets call it user.
Before the form gets filled in model = {}.
Once its filled in and valid, I would like to have this result
model = {
user:{
name:"TestName" //The value of the input
someCustomData: "Not part of the form"
someMoreMetaData: "Also not part of the from"
}
}
The resulting model having appended arbitrary meta-data once user entered a valid name. Thus creating a "user specific model"
So basically, I want my validation function to push the result to the model.
How would I approach this, for the key has to be bound to a property of a object that will only exist once validation returns true.
{
key: //what do I bind to?
type: 'USER',
templateOptions: {
required: true,
type: 'text'
},
validators:{
isValid: function($viewValue, $modelValue, scope){
var value = $modelValue || $viewValue;
if (validateName(value)){
scope.model.user = { name: viewValue, date:....}
return true;
}
}
}
}
If possible, please nudge me in the right direction..Still pretty novice.
$scope.user
{
'name':'TestName',
'someCustomData': 'Not part of the form',
'someMoreMetaData': 'Also not part of the from'
}
You should add a watch validation and act when valid:
scope.$watch('myForm.myInput.$valid', function(validity) {$scope.user.custom data ="blabla"})

sails.js beforeCreate method receives only the model properties on which required is set to true

This is a model in sails.js.
module.exports = {
attributes: {
name: {
type: "string"
},
email: {
type: "email",
required: true
},
password: {
type: "string"
}
},
beforeCreate: function(values, next) {
console.log(values); //logs {email:"mail#someplace.com"}
console.log(values.email); // logs the email id sent via post
console.log(values.password); // logs undefined is required is set to false, If required is set to true, password will log properly.
next();
}
};
I am planning to perform some encrypting of the password in the beforeCreate function, Of course I will be requiring the password and can continue with it right now but how to manage this for optional values just in case the need arises?
I found the reason for the above issue,
In my controller, I was doing a create record but the record I was creating contained only one field, i.e the email see below:
Users.create({email:req.body.email}).exec(function(err, user){
// user created
});
The model is directly mapped with the object being inserted into the database, So internally sails removes/ignores the fields which are not present.
To not remove these empty fields, you might have to set schema:true in your model.

Angular - how to do a dynamic alert with different type of errors?

In my web app I am showing alerts for loading content and another alert when there is an error.
I am using AngularJS/AngularStrap
right now this is what I have:
var noLinesAlert = $alert({
title: 'Sorry, no lines at this moment :(',
container: '.no-lines-alert',
type: 'danger',
dismissable: false,
show: false
}),
loaderAlert = $alert({
content: 'Loading content. Please wait...',
container: '.alerts-container',
type: 'info',
show: true
});
and I am calling these alerts this way, example noLinesAlert.show() or loaderAlert.hide() and so on... but just imagine that I will have 4 more alerts for special cases, do I have to create those vars with the object inside every time or is there a way to do it more programmatically? and how.
Well, you can store "default" settings for those 4 special cases in dedicated object, for example: ALERTS_SETTINGS = { notice: {} }, and then the call would be much more concise:
var noticeOptions = angular.extend(ALERTS_SETTINGS.notice, { title: "My custom property one" })
var notice = $alert(noticeOptions)
Using angular.extend you can override any default setting from the original object by those you provide in the second argument.
For every alert the title, container, type, and show property changes. You need to give the program that information, so you can't do this any shorter.
Make it dynamic by putting the $alert into a function. Allow the function to pass parameters to the $alert. Set the show property to true.
Call the function when needed. For example:
var displayAlert = function(alertTitle, className) {
var alertOptions = {
title: alertTitle,
container: className,
type: 'danger',
dismissable: false,
show: true
};
var alert = $alert(alertOptions);
}
// Call the function when needed
$scope.displayAlert('Sorry, no lines at this moment :(','.no-lines-alert')

Add multiple records to model's collection Sailsjs

I have the following models in my Sailsjs application with a many-to-many relationship:
event.js:
attributes: {
title : { type: 'string', required: true },
description : { type: 'string', required: true },
location : { type: 'string', required: true },
maxMembers : { type: 'integer', required: true },
currentMembers : { collection: 'user', via: 'eventsAttending', dominant: true },
creator : { model: 'user', required: true },
invitations : { collection: 'invitation', via: 'eventID' },
tags : { collection: 'tag', via: 'taggedEvents', dominant: true },
lat : { type: 'float' },
lon : { type: 'float' },
},
tags.js:
attributes: {
tagName : { type: 'string', unique: true, required: true },
taggedEvents : { collection: 'event', via: 'tags' },
},
Based on the documentation, this relationship looks correct. I have the following method in tag.js that accepts an array of tag strings, and an event id, and is supposed to add or remove the tags that were passed in:
modifyTags: function (tags, eventId) {
var tagRecords = [];
_.forEach(tags, function(tag) {
Tag.findOrCreate({tagName: tag}, {tagName: tag}, function (error, result) {
tagRecords.push({id: result.id})
})
})
Event.findOneById(eventId).populate('tags').exec(function(error, event){
console.log(event)
var currentTags = event.tags;
console.log(currentTags)
delete currentTags.add;
delete currentTags.remove;
if (currentTags.length > 0) {
currentTags = _.pluck(currentTags, 'id');
}
var modifiedTags = _.pluck(tagRecords, 'id');
var tagsToAdd = _.difference(modifiedTags, currentTags);
var tagsToRemove = _.difference(currentTags, modifiedTags);
console.log('current', currentTags)
console.log('remove', tagsToRemove)
console.log('add', tagsToAdd)
if (tagsToAdd.length > 0) {
_.forEach(tagsToAdd, function (tag) {
event.tags.add(tag);
})
event.save(console.log)
}
if (tagsToRemove.length > 0) {
_.forEach(tagsToRemove, function (tagId) {
event.tags.remove(tagId)
})
event.save()
}
})
}
This is how the method is called from the event model:
afterCreate: function(record, next) {
Tag.modifyTags(tags, record.id)
next();
}
When I post to event/create, I get this result: http://pastebin.com/PMiqBbfR.
It looks as if the method call itself is looped over, rather than just the tagsToAdd or tagsToRemove array. Whats more confusing is that at the end, in the last log of the event, it looks like the event has the correct tags. When I then post to event/1, however, the tags array is empty. I've also tried saving immediately after each .add(), but still get similar results.
Ideally, I'd like to loop over both the tagsToAdd and tagsToRemove arrays, modify their ids in the model's collection, and then call .save() once on the model.
I have spent a ton of time trying to debug this, so any help would be greatly appreciated!
There are a few problems with your implementation, but the main issue is that you're treating certain methods--namely .save() and .findOrCreate as synchronous methods, when they are (like all Waterline methods) asynchronous, requiring a callback. So you're effectively running a bunch of code in parallel and not waiting for it to finish before returning.
Also, since it seems like what you're trying to do is replace the current event tags with this new list, the method you came up with is a bit over-engineered--you don't need to use event.tags.add and event.tags.remove. You can just use plain old update.
So you could probably rewrite the modifyTags method as:
modifyTags: function (tags, eventId, mainCb) {
// Asynchronously transform the `tags` array into an array of Tag records
async.map(tags, function(tag, cb) {
// For each tag, find or create a new record.
// Since the async.map `cb` argument expects a function with
// the standard (error, result) node signature, this will add
// the new (or existing) Tag instance to the resulting array.
// If an error occurs, async.map will exit early and call the
// "done()" function below
Tag.findOrCreate({tagName: tag}, {tagName: tag}, cb);
}, function done (err, tagRecords) {
if (err) {return mainCb(err);}
// Update the event with the new tags
Event.update({id: eventId}, {tags: tagRecords}).exec(mainCb);
});
}
See the full docs for async.map here.
If you wanted to stick with your implementation using .add and .remove, you would still want to use async.map, and do the rest of your logic in the done method. You don't need two .save calls; just do run all the .add and .remove code first, then do a single .save(mainCb) to finish it off.
And I don't know what you're trying to accomplish by deleting the .add and .remove methods from currentTags (which is a direct reference to event.tags), but it won't work and will just cause confusion later!

Categories

Resources