Mark fields not valid as red with knockout/durandal
Asked Answered
W

1

3

I am learning knockout, Durandal and Breeze and thanks to the JumpStart SPA video from John Papa this is a pleasure. So I examine the code of this project and right now I am trying to change the validation mechanism.

At this time, when saving changes, if the save failed, we collect the errors and display a toast with a resume of the errors.

enter image description here

What I would like to achieve is:

  • having the ability to mark fields not valid in red (background-color) in the view

  • when a toast is displayed with the validation errors resume (something like: 'Save failed: affairNr is required' ) I would like to replace the property name with a more friendly name (something like 'Save failed: affair number is required')

Here is the portion code for the validation in the datacontext.js:

var saveChanges = function () {
    return manager.saveChanges()
        .then(saveSucceeded)
        .fail(saveFailed);

    function saveSucceeded(saveResult) {
        log('Saved data successfully', saveResult, true);
    }

    function saveFailed(error) {
        var msg = 'Save failed: ' + getErrorMessages(error);
        logError(msg, error);
        error.message = msg;
        throw error;
    }
};

function getErrorMessages(error) {
    var msg = error.message;
    if (msg.match(/validation error/i)) {
        return getValidationMessages(error);
    }
    return msg;
}

function getValidationMessages(error) {
    try {
        //foreach entity with a validation error
        return error.entitiesWithErrors.map(function (entity) {
            // get each validation error
            return entity.entityAspect.getValidationErrors().map(function (valError) {
                // return the error message from the validation
                return valError.errorMessage;
            }).join('; <br/>');
        }).join('; <br/>');
    }
    catch (e) { }
    return 'validation error';
}

Does someone can point me in the right direction?

Thanks in advance.


EDIT:

To reproduce the problem: click on the left sidebar on Transports + recherche avancee + any item in the list + on the right side: clear some inputs (like Numero d'affaire as screenshot below) then click on 'Enregistrer'. Then the save button is called. There I need to check if there are invalid inputs with ko.validation.group but it doesn't work.

enter image description here

Whore answered 26/3, 2013 at 11:30 Comment(1)
If you're trying to maintain good view/view model separation and MVVM pattern on the client-side, I think Knockout Validation that Evan references is a good choice, because it validates the view model data.Damiano
C
8

Some time ago, I posted a helper function that extend observable properties to add validation based on the breeze validators. Using that helper you can achieve the red color on the invalid inputs:

Translate breeze validation messages

I can't help you with the second question, I know that you can customize the validation message but I think that with the default validators you can't set a friendly name to show on the message.

Update:

The first that I do is to create a helper module that exposes the function (updated version):

define([],
function () {
"use strict";
var foreignKeyInvalidValue = 0;

function addValidationRules(entity) {
    var entityType = entity.entityType,
        i,
        property,
        propertyName,
        propertyObject,
        validators,
        u,
        validator,
        nValidator;

    if (entityType) {
        for (i = 0; i < entityType.dataProperties.length; i += 1) {
            property = entityType.dataProperties[i];
            propertyName = property.name;
            propertyObject = entity[propertyName];
            validators = [];

            for (u = 0; u < property.validators.length; u += 1) {
                validator = property.validators[u];
                nValidator = {
                    propertyName: propertyName,
                    validator: function (val) {
                        var error = this.innerValidator.validate(val, { displayName: this.propertyName });
                        this.message = error ? error.errorMessage : "";
                        return error === null;
                    },
                    message: "",
                    innerValidator: validator
                };
                validators.push(nValidator);
            }
            propertyObject.extend({
                validation: validators
            });
        }

        for (i = 0; i < entityType.foreignKeyProperties.length; i += 1) {
            property = entityType.foreignKeyProperties[i];
            propertyName = property.name;
            propertyObject = entity[propertyName];

            validators = [];
            for (u = 0; u < property.validators.length; u += 1) {
                validator = property.validators[u];
                nValidator = {
                    propertyName: propertyName,
                    validator: function (val) {
                        var error = this.innerValidator.validate(val, { displayName: this.propertyName });
                        this.message = error ? error.errorMessage : "";
                        return error === null;
                    },
                    message: "",
                    innerValidator: validator
                };
                validators.push(nValidator);
            }
            propertyObject.extend({
                validation: validators
            });
            if (!property.isNullable) {
                //Bussiness Rule: 0 is not allowed for required foreign keys
                propertyObject.extend({ notEqual: foreignKeyInvalidValue });
            }
        }
    }
}

return {
    addValidationRules: addValidationRules
};
});

Then, I'm defining an initializer for each breeze entity type ( http://www.breezejs.com/documentation/extending-entities ). Example:

define(['app/validatorHelper', 'knockout'],
function (vHelper, ko) {
"use strict";
var constructor = function () {
},

    initializer = function indicadorInitializer(entity) {
        vHelper.addValidationRules(entity);
    };

return {
    constructor: constructor,
    initializer: initializer
};
});

And finally, somewhere ( I'm doing it on an init function inside my dataservice module ), I'm registering the initializer ( http://www.breezejs.com/documentation/extending-entities ) :

//store comes from: manager = breezeconfig.createManager(),
//    store = manager.metadataStore,
store.registerEntityTypeCtor("Palanca", domain.palanca.constructor, domain.palanca.initializer);

I'm doing all of this before fetching the metadata.

I hope that this would help you.

Update 2:

I've found the problem, your version of knockout validation is not the last one.

Inside the downloaded file from: http://ericmbarnard.github.com/Knockout-Validation/ the line: 349 is:

exports.rules[ruleName] = ruleObj;

Inside your file, the equivalent line ( function addAnonymousRule ) is:

ko.validation.rules[ruleName] = {
    validator: ruleObj.validator,
    message: ruleObj.message || 'Error'
};

I think that with the lastest version should work.

Update 4:

This is the code to save:

vmAddPalanca.prototype.saveChangesCmd = ko.asyncCommand({
    execute: function (palanca, complete) {
        var validationErrors = ko.validation.group(palanca);
        if (validationErrors().length === 0) {
            dataservice.saveChanges()
            .then(saveChangesSuccess)
            .fail(saveChangesFail)
            .fin(complete);
        } else {
            validationErrors.showAllMessages(true);
            toastr.error("Debe corregir los errores antes de poder guardar");
            complete();
        }
    },
    canExecute: function (isExecuting) {
        return !isExecuting && dataservice.hasChanges();
    }
});
Chivers answered 26/3, 2013 at 14:39 Comment(16)
I'm busy trying to debug your code and I got an error on: propertyObject.extend({ validation: validators }); saying error retrieving data.Whore
Maybe some background is needed. There is a previous post where I show how I'm using the validation helper : #13662946 The basic is that I'm using an initializer for my breeze entities and inside the initializer I'm calling the function. I'll update my answer to give you a detailed answer.Nikolos
Yes, I added knockout validation. In fact, for the 1st version of your code (with indicadorInitializer) I got no error. But with the 2nd version of your code (with addValidationRules) I got the error I give above. Any idea why?Whore
I have no idea, if you can put the code somewhere, i can give it a look.Nikolos
Here is a link to download a zip containing the VS2012 solution: dropbox.com/s/rgtkm5xkkoem2cb/TRANSPORTBOEK.zip Simply open the solution, run it, on the left sidebar, click 'Transports' + 'Recherche avancée' then it will gives an error. The problem is located in App/services/model.js line 64 + 90. Thanks for your help. If I comment these lines and replace with the line below then no errors.Whore
I've found the problem, your version of knockout validation is not the last one. See my updates answer.Nikolos
You are right. Thank you very much! Your help was precious. It is a pity we cannot have the translations back from the server side (attributes on the properties).Whore
I have another question for you: based on your code which works perfectly for getting validations rules from metadata (based on breeze), now I would like to block the 'save' in my view if some elements are invalid. I try since this morning without luck. Let's say I have en object 'transport' in my viewModel which contains some properties to validate. When I click on the save button on my view I would like to check if some properties of my 'transport' object are invalid. Any idea?Whore
I've updated the answer with a save example. "palanca" is my entity.Nikolos
Yes as you suggest I try using ko.validation.group but in my solution it doesn't work and I don't know why. I'm busy on this problem since this morning :( Validation is ok on input fields ('blah blah' is required) but on the save button I am not able to check whether there are invalid inputs (by counting on the group). May I ask you again to check in my solution? Here it is: dropbox.com/s/rgtkm5xkkoem2cb/TRANSPORTBOEK.zip This drives me crazy!Whore
I updated my question to show how to reproduce the problem. Anyway, I thank you very much for what you already do for me.Whore
Your problem is the knockout validation config. In main.js you have: grouping: { deep: true, observable: true }. You need to put deep: false. Why? Because breeze holds a reference to the entity inside the entityAspect property, that has a entityAspect property that holds a reference to the entity... In short: a recursivity problem. With that change, the line: var validationErrors = ko.validation.group(vm.transport); should work.Nikolos
You are a king. Thank you very much! I appreciate your help.Whore
@Yuste: hello again :) I published a new post today related with a validation problem. Maybe you can take a look if you had time: #15720421 Thanks anyway.Whore
Ooops wrong SO link. Here it is: #15728388Whore
Hi, any chance someone could post a full example of the entity initializer part of this? I'm a little confused on the initializer that appears to be in a separate module, and the whole domain.entity.constructor bit? I'm fine with the concept of extending entity constructors but what's the domain bit and how is it referenced? Thanks.Jaborandi

© 2022 - 2024 — McMap. All rights reserved.