1. #1
    Sencha Premium Member tobiu's Avatar
    Join Date
    May 2007
    Location
    Munich (Germany)
    Posts
    2,695
    Vote Rating
    114
    tobiu is a name known to all tobiu is a name known to all tobiu is a name known to all tobiu is a name known to all tobiu is a name known to all tobiu is a name known to all

      0  

    Default Ext 4: Validating a Form with a Model

    Ext 4: Validating a Form with a Model


    hi team and community,

    this is meant as a constructive feedback topic and a discussion about a feature request.
    when i first saw the news for ext 4, telling me validations inside a model, i thought "cool", i can create multiple forms and just have to specify the rules at one spot.

    lets create a simple usecase: an addCustomer form.

    Code:
    Ext.define('app.model.Customer', {
        extend : 'Ext.data.Model',
    
        fields : [
            {name: 'id',   type: 'int'},
            {name: 'name', type: 'string'}
        ],
    
        validations: [
            {type: 'presence', field: 'name'},
            {type: 'length',   field: 'name',  min: 2}
        ]
    });
    Code:
    Ext.define('app.view.customer.Add', {
        extend : 'Ext.window.Window',
        alias  : 'widget.view_customer_Add',
    
        autoShow : true,
        height      : 300,
        layout      : 'fit',
        title         : "Add Customer",
        width       : 400,
    
        initComponent: function() {
    
            this.items = [
                {
                    xtype  : 'form',
                    border : false,
    
                    defaults : {
                        xtype       : 'textfield',
                        labelWidth  : 130
                    },
                    items: [
                        {
                            fieldLabel : _translate("Name"),
                            name       : 'name'
                        }
                    ]
                }
            ];
    
            this.buttons = [
                {
                    action  : 'save',
                    text    : 'Save',
                    handler : function() {
                        var form = this.up('window').down('form').getForm();
                        console.log(form.getValues());
    
                        var customer = Ext.ModelManager.create(
                            form.getValues(),
                            'app.model.Customer'
                        );
    
                        var errors = customer.validate();
    
                        console.log(errors);
    
                        form.loadRecord(customer);
    
                        //form.isValid();
    
                        form.markInvalid(errors);
    
                        console.log(form.isValid());
                    }
                }
            ];
    
            this.callParent(arguments);
        }
    });
    what i do inside the handler-function feels like a bad workaround for me:
    1. getting the values of the form
    2. creating a model-instance (record) with these values
    3. validating the record
    4. using the error-object to mark the form fields invalid

    if you use form.isValid(), it will always say true, since the fields have no "own" errors.
    so you would have to check vs the updated record each time.

    a strong feature request would be, to unify the error types and error texts between form fields and models. example

    Code:
    allowBlank : false (field)
    {type: 'presence', field: 'name'} (model)
    if you display the model validation inside the field, you will get the error message:

    must be present
    at least the right field gets the error, but the message with allowBlank: false is way better.

    personal wishlist:

    what would be super awesome is, if you could specify models inside a formPanel, like you can do in a store:

    Code:
    {
        xtype : 'form',
        model : 'Customer'
    }
    so that the model validations get automatically inculded into the form validation.


    feedback appreciated!


    best regards
    tobiu
    Best regards
    Tobias Uhlig
    __________

    S-CIRCLES Social Network Engine

  2. #2
    Sencha User
    Join Date
    Mar 2011
    Location
    Germany
    Posts
    198
    Vote Rating
    1
    Nickname is on a distinguished road

      0  

    Default


    Hello,

    had the same idea, but came up with a lazy solution.

    I'm not sure if this can be transformed to work with "addCustomer" (i.e. blank form on initialisation), but if I edit a selected customer (or system user in my case) from a grid it seems to work.

    What I do (before editing a user):
    - a gridpanel lists all users and of the course, the bound store has a Model
    - select a user from the grid and I hit my edit button
    - a form is generated and from the edit button click listener I use "form.loadRecord(record_from_grid_with_grids_selectionModel)"

    Now when I hit the "save form" button, I do the following

    PHP Code:
        onSaveUserEdit: function(btnevent) {
            var 
    me this,
                
    form btn.up('form').getForm(),
                
    errors form.updateRecord(form.getRecord()).getRecord().validate();  // voodoo!
            
    if(errors.isValid()) {
                
    // form is valid
                
    form.submit(/** custom form submit action object **/);
            } else {
                
    form.markInvalid(errors);
            }
        } 
    I didn't test this with all variations and if this really works in any case.

    For your example case I could think of something like

    PHP Code:
    var form Ext.create('My.Custom.Add.Form');
    form.loadRecord(Ext.ModelManager.get('MyModelDefinition')); // pseudo code, untested 
    This should bind an empty record to the form and after the user edited the form, BasicForm can use the existing empty Record, update the data fields in the record with the fieldvalues and perform the validation on the record.

    Hopefully, this is understandable

    Your suggestion sound like a solid solution (model config option in form).
    But with MVC the form is only a visual representation of a Record and the Controller (in your example the button handler) should worry which model is used and validated against the "visual representation".

    Appreciate your clear and gracefully way of proposing optimization to the framework

  3. #3
    Sencha User ykey's Avatar
    Join Date
    Mar 2010
    Location
    USA
    Posts
    245
    Vote Rating
    27
    ykey has a spectacular aura about ykey has a spectacular aura about

      0  

    Default


    I have looked into this previously and came up with something at the field level so I could conditionally enable model bindings in my form.

    http://www.sencha.com/forum/showthre...l=1#post599286

    What I really want to see is cross field model validations so I can validate a record's field based on values in the records other fields (start date before end date kind of validation).

    How would your approach work with forms that might represent an associated model structure (multiple associated models)?

  4. #4
    Sencha User
    Join Date
    May 2009
    Location
    Germany
    Posts
    46
    Vote Rating
    2
    quixit is on a distinguished road

      0  

    Default Another Solution to make a form fields use the model validators

    Another Solution to make a form fields use the model validators


    This solution makes usage of the Form Field native validation features.
    For using this feature, set a attribute "model" in the Form Panel with either the model classname, or the Model Class itself.

    It smoothly integrates in ExtJS 4 by overriding and mixin.

    PHP Code:
    /**
     * Thomas Lauria - forms use model validations
     */
    Ext.override(Ext.form.Panel, {
    // hook in, on every Field by the mixin Ext.form.FieldAncestor onFieldAdded method
      
    onFieldAdded: function(field) {
        
    this.applyModelValidation(field);
        
    this.callOverridden(arguments);
      }
    });

    /**
     * @class Ext.form.FieldModelValidation
    A mixin for {@link Ext.form.Panel} components, adding the validations of a given model to the fields of the panel
     * @docauthor Thomas Lauria <t.lauria@last-it.de>
     */
    Ext.define('Ext.form.FieldModelValidation', {
        
    /**
         * @protected applies the validations of the model configured in the form panel to the given field
         * @param {Ext.form.field.Base} field The field to apply the validations
         */
        
    applyModelValidation: function(field) {
            var 
    me this,
            
    name field.name,
            
    noValidations Ext.isDefined(me.modelValidations) && me.modelValidations === false;
            if(
    noValidations || !name || ! me.model) {
              return;
            }
            if(
    Ext.isString(me.model)) {
              
    me.model Ext.ModelManager.getModel(me.model);
            }
            if(!
    me.modelValidations) {
              
    me.initModelValidations();
            }
            if(
    me.modelValidations[name]) {
              
    me.setFieldValidationByModel(fieldme.modelValidations[name]);
            }
        },

        
    /**
         * @protected reorganize the model validations and store them internally
         */
        
    initModelValidations: function() {
          var 
    me this,
          
    validations me.model.prototype.validations,
          
    mv = {};
          if(!
    validations || validations.length == 0){
            
    me.modelValidations false;
            return;
          }
          
    Ext.each(validations, function(v) {
            if(!
    mv[v.field]) {
              
    mv[v.field] = [];
            }
            
    mv[v.field].push(v);
          });
          
    me.modelValidations mv;
        },
        
        
        
    /**
         * @protected helper function to apply the validations
         * @param {Ext.form.field.Base} field The field to apply the validations
         * @param {Array} validations An array with the validations matching the field.name
         */
        
    setFieldValidationByModel: function (fieldvalidations) {
          
    Ext.each(validations, function(v){
            switch (
    v.type) {
              case 
    'presence':
                
    field.allowBlank false;
                return;
              case 
    'length':
                var 
    isNumeric Ext.isDefined(field.maxValue) || Ext.isDefined(field.minValue),
                
    target isNumeric 'Value' 'Length';
                if(
    v.min) {
                  
    field['min'+target] = v.min;
                }
                if(
    v.max) {
                  
    field['max'+target] = v.max;
                }
                return;
              case 
    'email':
                
    field.vtype 'email';
                return;
              case 
    'format':
                
    field.regex v.matcher;
                return;
              
    //@todo
              //case 'inclusion':
              //case 'exclusion':
            
    }
          });
        }
    });

    Ext.form.Panel.mixin('FieldModelValidation'Ext.form.FieldModelValidation); 

  5. #5
    Ext JS Premium Member devtig's Avatar
    Join Date
    Jan 2010
    Location
    Rotterdam, The Netherlands
    Posts
    394
    Vote Rating
    14
    devtig will become famous soon enough

      0  

    Default


    Quote Originally Posted by tobiu View Post
    Code:
    Ext.define('app.view.customer.Add', {
        extend : 'Ext.window.Window',
        alias  : 'widget.view_customer_Add',
    
        autoShow : true,
        height      : 300,
        layout      : 'fit',
        title         : "Add Customer",
        width       : 400,
    
        initComponent: function() {
    
            this.items = [....];
    
            this.buttons = [
                {
                    action  : 'save',
                    text    : 'Save',
                    handler : function() {
                        var form = this.up('window').down('form').getForm();
                        console.log(form.getValues());
    
                        var customer = Ext.ModelManager.create(
                            form.getValues(),
                            'app.model.Customer'
                        );
                         var errors = customer.validate();
                         console.log(errors);
                         form.loadRecord(customer);
                         //form.isValid();
                         form.markInvalid(errors);
                         console.log(form.isValid());
                    }
                }
            ];
    
            this.callParent(arguments);
        }
    });
    Hi Tobiu, why do you do the form.loadRecord(customer)? Isn't the form still populated with the entered values?

  6. #6
    Sencha User
    Join Date
    Apr 2010
    Posts
    79
    Vote Rating
    6
    HriBB is on a distinguished road

      0  

    Default


    My implementation
    Code:
    Ext.define('PBO.override.form.field.Base', {    override: 'Ext.form.field.Base',
        
        getErrors: function() {
            var errors = this.callParent(arguments),
                record = this.up('form').getRecord(),
                validators = Ext.data.validations,
                validation, field, i;
                
            if (!record || !record.validations || !record.validations.length) {
                return errors;
            }
            
            for (i = 0; i < record.validations.length; i++) {
                validation = record.validations[i];
                field = validation.field || validation.name;
                if (field === this.name && !validators[validation.type](validation, this.getRawValue())) {
                    errors.push(validation.message || validators[validation.type + 'Message']);
                }
            }
            
            return errors;
        }
    });

  7. #7
    Ext Premium Member halcwb's Avatar
    Join Date
    Mar 2010
    Location
    Rotterdam
    Posts
    386
    Vote Rating
    55
    halcwb is a jewel in the rough halcwb is a jewel in the rough halcwb is a jewel in the rough

      0  

    Default Having validations in one place

    Having validations in one place


    To me the important issue is that validation is business logic, and views shouldn't be aware of business logic. Also, in the majority of cases creating models in the GUI, to me makes no sense. Only when your app is really the Ext app, it makes sense. But in a normal server client situation, the business logic should reside on the server side. Otherwise it defies the MVC pattern.

    Therefore, I have a validator that just loads a model from the server and the server returns how the model should be validated like:

    Code:
    {
    • "success": true,
    • - - "data": [
      • - - {
        • "Name": "ImportDto",
        • - - "Props": [
          • - - {
            • "Name": "GStandDbName",
            • - - "Validations": [
              • - - {
                • "Type": "min",
                • - - "Arguments": [
                  • "2"
                • ]
              • }
            • ]
          • },
          • - - {
            • "Name": "GStandDbPassword",
            • "Validations": [ ]
          • },
          • - - {
            • "Name": "GStandDbServer",
            • "Validations": [ ]
          • },
          • - - {
            • "Name": "GStandDbUser",
            • "Validations": [ ]
          • },
          • - - {
            • "Name": "GStandDirectory",
            • - - "Validations": [
              • - - {
                • "Type": "directory",
                • "Arguments": [ ]
              • }
            • ]
          • },
          • - - {
            • "Name": "RavenDbName",
            • "Validations": [ ]
          • },
          • - - {
            • "Name": "RavenDbPassword",
            • "Validations": [ ]
          • },
          • - - {
            • "Name": "RavenDbUrl",
            • - - "Validations": [
              • - - {
                • "Type": "url",
                • "Arguments": [ ]
              • }
            • ]
          • },
          • - - {
            • "Name": "RavenDbUser",
            • "Validations": [ ]
          • }
        • ]
      • }
    • ],
    • "total": 1,
    • "message": ""
    }
    The validator takes the model description an applies the validations to the form like this:

    Code:
    Ext.define('GenPresWebView.util.Validator', {
        extend: 'Ext.Base',
    
    
        statics: {
            validate: function (fields, model) {
                var allowBlank, minLength, maxLength, url, regex, directory,
                    fields = fields || [];
    
    
                Ext.Array.forEach(fields, function (field) {
                    var prop = Ext.Array.findBy(model.Props, function (prop) {
                        return prop.Name === field.name;
                    });
    
    
                    if (prop) {
                        allowBlank = !prop.Validations.some(function (validation) {
                            return validation.Type === "required";
                        });
                        field.allowBlank = allowBlank;
    
    
                        minLength = Ext.Array.findBy(prop.Validations, function (validation) {
                            return validation.Type === 'min';
                        });
                        if (minLength) {
                            field.minLength = parseInt(minLength.Arguments[0]);
                        }
    
    
                        maxLength = Ext.Array.findBy(prop.Validations, function (validation) {
                            return validation.Type === 'max';
                        });
                        if (maxLength) {
                            field.maxLength = parseInt(maxLength.Arguments[0]);
                        }
    
    
                        url = Ext.Array.findBy(prop.Validations, function (validation) {
                            return validation.Type === 'url';
                        });
                        if (url) {
                            field.vtype = 'url';
                        }
    
    
                        directory = Ext.Array.findBy(prop.Validations, function (validation) {
                            return validation.Type === 'directory';
                        });
                        if (directory) {
                            field.regex = /^([a-zA-Z]:)?(\\[^<>:"/\\|?*]+)+\\?$/;
                            field.regexText = "Not a valid directory"
                        }
                    }
                });
            }
        }
    });

  8. #8
    Sencha Premium Member
    Join Date
    Oct 2013
    Posts
    4
    Vote Rating
    0
    romankuzmik is on a distinguished road

      0  

    Default Fix with ExtJS 4.2 for Thomas Lauria override

    Fix with ExtJS 4.2 for Thomas Lauria override


    PHP Code:
    /** * Thomas Lauria - forms use model validations * with Ext 4.2 patches by Roman Kuzmik * * http://www.sencha.com/forum/showthread.php?135732-Ext-4-Validating-a-Form-with-a-Model&p=778233#post778233 * http://www.sencha.com/forum/showthread.php?125242-Form-lt-gt-Model-binding/page5 */Ext.override(Ext.form.Basic, {// hook in, on every Field by the mixin Ext.form.FieldAncestor onFieldAdded method  onFieldAdd: function(field) {    this.applyModelValidation(field);    this.callOverridden(arguments);  }});
    /** * @class Ext.form.FieldModelValidationA mixin for {@link Ext.form.Panel} components, adding the validations of a given model to the fields of the panel * @docauthor Thomas Lauria <t.lauria@last-it.de> */
    Ext.define('Ext.form.FieldModelValidation', {    /**     * @protected applies the validations of the model configured in the form panel to the given field     * @param {Ext.form.field.Base} field The field to apply the validations     */    applyModelValidation: function(field) {        var me this,        name field.name,        noValidations Ext.isDefined(me.modelValidations) && me.modelValidations === false;        if(noValidations || !name || ! me.owner.model) {          return;        }        if(Ext.isString(me.owner.model)) {          me.owner.model Ext.ModelManager.getModel(me.owner.model);        }        if(!me.modelValidations) {          me.initModelValidations();        }        if(me.modelValidations[name]) {          me.setFieldValidationByModel(fieldme.modelValidations[name]);        }    },
        
    /**     * @protected reorganize the model validations and store them internally     */    initModelValidations: function() {      var me this,      validations me.owner.model.prototype.validations,      mv = {};      if(!validations || validations.length == 0){        me.modelValidations false;        return;      }      Ext.each(validations, function(v) {        if(!mv[v.field]) {          mv[v.field] = [];        }        mv[v.field].push(v);      });      me.modelValidations mv;    },
        
    /**     * @protected helper function to apply the validations     * @param {Ext.form.field.Base} field The field to apply the validations     * @param {Array} validations An array with the validations matching the field.name     */    setFieldValidationByModel: function (fieldvalidations) {      Ext.each(validations, function(v){        switch (v.type) {          case 'presence':            field.allowBlank false;            return;          case 'length':            var isNumeric Ext.isDefined(field.maxValue) || Ext.isDefined(field.minValue),            target isNumeric 'Value' 'Length';            if(v.min) {              field['min'+target] = v.min;            }            if(v.max) {              field['max'+target] = v.max;            }            return;          case 'email':            field.vtype 'email';            return;          case 'format':            field.regex v.matcher;            return;          //@todo          //case 'inclusion':          //case 'exclusion':        }      });    }});
    Ext.form.Basic.mixin('FieldModelValidation'Ext.form.FieldModelValidation);