Edit and validate question

Edit and validate question

ztevieztevie Posts: 101Questions: 23Answers: 5
edited November 2016 in Free community support

Hi!
So I bought the Editor last week and has made great progress so far. I have no test site since I develop locally so i can't give a link. But hopefully this is something easy so I provide the code below.
The problem is: I have a field with 2 validators. One is a regex function that ensure the field contains excactly 6 numbers. The other is just a "unique" validation.
My editing on the client side is a bubble editing all fields. If I click Edit for a row the modal opens up as expected. If I click save without editing anything, I will get a validation warning "Please insert a valid employee number" even though it's valid and I havent changed it? If I enter another number validation works as expected, that is, if it's valid the edit will go through saving, if not valid I will get the warning.
It's like it sends an empty string, like when using required? But I don't have that validation. I don't have notEmpty either.

I would expect the formoptions: "changed" not sending that field at all if not changed? But to me it seems formOption doesn't work?
Maybe my custom regex validation treats the field as if it's supposed to be "required" and therefore refuses to validate a empty field even though I put 'changed' in formOptions?

Here's the php code for that field, and the editor javascript:

Field::inst( 'users.user_employee' )
            ->validator( function ( $val, $data, $opts ) {
                return !preg_match("/^[0-9]{6}\z/", $val) ?
                'Please insert a valid employee number' :
                true;
            } ) 
            ->validator( 'Validate::unique', array(
            'message' => 'The user already exists'
            ) ),
editor = new $.fn.dataTable.Editor( {
        ajax: "../_includes/process_adminusers.php",
        table: "#tbl-admin-users",
        formOptions: {
            main: {
                submit: 'changed'
            }
        },
        fields: [ {
                label: "Anst.nr:",
                name:  "users.user_employee"
            }, {
                label: "Förnamn:",
                name:  "users.user_fname"  
            }, {
                label: "Efternamn:",
                name:  "users.user_lname"
            }, {
                label: "Smeknamn:",
                name:  "users.user_nickname"
            }, {
                label: "Arbetsgrupp:",
                name:  "users.workteam_id",
                type:  "select"
            }, {
                label: "Yrke:",
                name:  "users.workdesc_id",
                type:  "select"
            }, {
                "label": "Behörighet:",
                "name": "roles[].role_id",
                "type": "checkbox"
            }
        ]
    } );

This question has an accepted answers - jump to answer

Answers

  • allanallan Posts: 61,450Questions: 1Answers: 10,055 Site admin

    submit: 'changed'

    Only the changed values are being submitted, but the validator still runs. Because it isn't checking for that data point not being submitted, the regex fails and the error occurs.

    Since you are using a custom validator, the default actions of checking for required, empty etc in the options are not being done - that would be required as part of your code.

    In this case, checking for $val !== null should do the trick. If that is true, return true.

    Allan

  • ztevieztevie Posts: 101Questions: 23Answers: 5
    edited November 2016

    Thanks!

    But I got a little confused with $val !== null and return true? It seemed weird but maybe I didn't understand where you wanted me to put it? I did like this anda it seems to work, or am I missing something?

    Field::inst( 'users.user_employee' )
                ->validator( function ( $val, $data, $opts ) {
                    if($val === null){
                        return true;
                    }
                    else{
                        return !preg_match("/^[0-9]{6}\z/", $val) ? 'Not valid!' : true;
                    }
                } )
    

    So, using my own custom validation function will always run regardless of the 'changed' function? But 'unique seems to work anyway it seems?
    A nice feature would be if you could incorporate a regex method in the validator, so user can use whatever regex string, like: ->validator( 'Validate::regex ).

    Another quickie question, notEmpty don't seem to work for checkboxes field, I would need at least one checkbox out of five to be checked, but haven't found a way to validate that is the case?

    Thanks for a great product!

  • allanallan Posts: 61,450Questions: 1Answers: 10,055 Site admin
    Answer ✓

    Yup - what you have is exactly what I was suggesting. The validation functions always run so that you can require data be submitted for a field (if it didn't run where no data was submitted, there would be no way to require it!). The null value indicates that no data was submitted, so it is valid in those cases.

    A nice feature would be if you could incorporate a regex method in the validator, so user can use whatever regex string, like: ->validator( 'Validate::regex ).

    Good idea - thanks! Added to the list.

    Another quickie question, notEmpty don't seem to work for checkboxes field, I would need at least one checkbox out of five to be checked, but haven't found a way to validate that is the case?

    This is one of those confusing situations when you need to use the required() validator rather than notEmpty. When HTML checkboxes are not checked, they just don't submit anything. notEmpty will only check for fields which are submitted to be not empty!

    Allan

  • ztevieztevie Posts: 101Questions: 23Answers: 5
    edited November 2016

    Required won't do anything either... It's a Mjoin field with several checkboxes. Those checkboxes are shown as a ',' separated list in a table childrow. When edit or create they are available as checkboxes in a bubble editor which contain all fields of the table. What I understood from the docs Mjoin fields don't have the validator methods available?
    I tried to see what $val returns with this function:

    ->join(
            Mjoin::inst( 'roles' )
                ->link( 'users.user_id', 'permissions.user_id' )
                ->link( 'roles.role_id', 'permissions.role_id' )
                ->order( 'role_desc asc' )
                ->fields(
                    Field::inst( 'role_id' )
                        ->validator( function ( $val, $data, $opts ) {
                            return $val;
                        } )      
                        ->options( 'roles', 'role_id', 'role_desc' ),
                    Field::inst( 'role_desc' )
                )
        )
    

    And how it's included in the editor bubble, and when initiating the table:

    //editor bubble
    {
                    "label": "Behörighet:",
                    "name": "roles[].role_id",
                    "type": "checkbox"
                }
    
    //Here how it's initiated for the columns. Ends up in a datatables childrow
    { data: "roles", render: "[, ].role_desc", className: "none", orderable: false },
    
    

    When I have checkboxes checked it will return the highest role_id from the checked boxes. But when I don't check any of them, nothing is returned.
    That would be fine cause I could make a if in that function to check if $val is empty.
    But the problem is I can't catch that "empty". Tried to compare to null, empty(), !is_int(), "", <1 and others, but nothing.
    But that may not matter either, tried to return a random string instead of $val above, but nothing. Why would it return and show the contents of $val but not anything else? I'm in way over my head here I think... :smiley:

    EDIT: A less optimal solution if nothing else works would be to have one of the roles checkboxes always checked and disabled for unchecking, since in reality that role now should always be available for all users. But I understand a disabled checkbox is not sent to the server on submit, and for possible future functionality roles may change and this role could be obsolete.
    ALso I would need to hardcode that option to be checked which is not desireable.

  • allanallan Posts: 61,450Questions: 1Answers: 10,055 Site admin

    Hmmm - it being an Mjoin certainly makes it a bit more interesting. Validators do work for Mjoin fields, but the idea that you don't need to select anything for an Mjoin is baked into the libraries.

    What you would need to do for this is something like:

    if ( isset( $_POST['action'] ) && $_POST['action'] === 'create' ) {
      if ( ! isset( $_POST['data']['roles'] ) ) {
        echo json_encode( [
           "fieldErrors" => [
             "roles[].role_id" => "Please select at least one checkbox"
            ]
        ] );
        exit(0);
      }
    }
    

    Put that in before the Editor::inst(...) code.

    1.6 is going to make it easier to have generic validators, which will be useful for cases such as this.

    Allan

  • ztevieztevie Posts: 101Questions: 23Answers: 5

    Unfortunately it messed up other things. I understand your code perfectly and it makes sense, but it made the bubble modal window to freeze and the save button aswell. Had to refresh the browser to unfreeze...

    If we go back to the custom function for the validator, what does $val contain if no checkbox is checked? I can return $val if I check at least one box. Even if $val is not set or empty I should be able to check with isset($val) or === null or something else but nothing works...
    Consider this:

    ->validator( function($val, $data, $opts){
                            if($val === "6"){
                                return 'if condition';
                            }
                            else{
                                return 'else condition';
                            }
                        })
    

    If I choose the checkbox with id 6, I will get 'if condition' returned. If I check any other box I will get 'else condition' returned. If I don't check any box nothing is returned... And as I said, it doesn't matter what I do, if no checkbox is chosen, I can use isset, ==null, empty and others, it won't even run thru the if/else snippet...

  • allanallan Posts: 61,450Questions: 1Answers: 10,055 Site admin

    , but it made the bubble modal window to freeze and the save button aswell.

    What does the return from the server contain, as shown by your browser's developer tools?

    $val contain if no checkbox is checked?

    Since you are using an Mjoin, the validator won't actually be run, since having no inputs for the Mjoin is assumed to be valid. That's why you would need to use a little bit of extra code outside of the Editor class.

    Allan

  • ztevieztevie Posts: 101Questions: 23Answers: 5

    I see...
    Ok, the response from server is:

    Load the page: All the normal json data, no errors.

    Create new row, no box checked:
    fieldErrors: Object
    roles[].role_id: "Please select at least one checkbox"

    Create new row, box checked:
    fieldErrors: Object
    roles[].role_id: "Please select at least one checkbox"

    Same response on both as you see. Also, when I eceived that error, I can close down the modal, but it won't open again if I click New without refreshing browser.
    Also, in neither case will the other fields get validator returns, even though they are empty...
    Worth mentioning is I don't use the default New and Edit buttons. My Datatable javascript look like this:

    var editor;
    
    $(document).ready(function() {
        editor = new $.fn.dataTable.Editor( {
            ajax: "../_includes/process_adminusers.php",
            table: "#tbl-admin-users",
            formOptions: {
                main: {
                    submit: 'changed',
                    focus: null
                }
            },
            fields: [ {
                    label: "Anst.nr:",
                    name:  "users.user_employee"
                }, {
                    label: "Förnamn:",
                    name:  "users.user_fname"  
                }, {
                    label: "Efternamn:",
                    name:  "users.user_lname"
                }, {
                    label: "Smeknamn:",
                    name:  "users.user_nickname"
                }, {
                    label: "Arbetsgrupp:",
                    name:  "users.workteam_id",
                    type:  "select"
                }, {
                    label: "Yrke:",
                    name:  "users.workdesc_id",
                    type:  "select"
                }, {
                    "label": "Behörighet:",
                    "name": "roles[].role_id",
                    "type": "checkbox"
                }
            ]
        } );
        
        $('a.editor_create').on('click', function (e) {
            e.preventDefault();
     
            editor.create( {
                title: 'Skapa ny användare',
                buttons: 'Spara'
            } );
        } );
     
        $('#tbl-admin-users').on( 'click', 'a.remove', function (e) {
            editor
                .title( '<b>Ta bort användare</b>' )
                .message( 'Är du säker på att du vill ta bort användaren?' )
                .buttons( { "label": "Ta bort", "fn": function () { editor.submit() } } )
                .remove( $(this).closest('tr') );
        } );
        
        $('#tbl-admin-users').on( 'click', 'a.edit', function () {
            editor
                .title('<b>Redigera användare</b>')
                .buttons( { "label": "Spara", "fn": function () { editor.submit(); } } )
                .edit( $(this).closest('tr') );
        } );
        
     
            var eTable = $('#tbl-admin-users').DataTable( {
            language: {
                "url": "js/helpers/Swedish.json"
            },
            dom: 'rtip',
            responsive: true,
            processing: true,
            ajax: {
                url: "../_includes/process_adminusers.php",
                type: 'POST'
            },
            order: [ 1, 'asc' ],
            columns: [
                { data: "users.user_employee", responsivePriority: 1 },
                { data: null,
                    render: function(data, type, row){
                        return data.users.user_fname+' '+data.users.user_lname;
                    },
                    editField: ['user_fname', 'user_lname'],
                    responsivePriority: 4
                },
                { data: "users.user_nickname", responsivePriority: 7 },
                { data: "workteam.workteam_name", responsivePriority: 5 },
                { data: "workdesc.workdesc_name", responsivePriority: 6 },
                { data: "roles", render: "[, ].role_desc", className: "none", orderable: false },
                { data: null,
                    defaultContent: '<a href="#" class="edit"><i class="gi gi-pencil"><i/></a>',
                    orderable: false,
                    responsivePriority: 2
                },
                { data: null,
                    defaultContent: '<a href="#" class="remove"><i class="hi hi-remove" style="color:red;"><i/></a>',
                    orderable: false,
                    responsivePriority: 3
                }
            ],
            select: false,
            "initComplete": function( settings, json ) {
                fillSelect(json.options["users.workteam_id"], "Arbetsgrupp", "3");
                fillSelect(json.options["users.workdesc_id"], "Yrke", "4");
            }
        } );
        function fillSelect(obj, title, col){
            var objVal = "";
            var optString = "";
            optString = '<optgroup label="'+title+'" id="'+col+'">';
            $(obj).each(function(){
                objVal = this.label;
                optString += '<option value="'+objVal+'">'+objVal+'</option>';
            });
            optString += '</optgroup>';
            $("#select-adminusers").append(optString);
        };
        
        $("#select-adminusers").on('change', function(){
            var colToSearch = 0;
            colToSearch = $('#select-adminusers :selected').parent().attr('id');
            eTable.column([colToSearch]).search(this.value).draw();
            eTable.column([colToSearch]).search('');
        });
    } );
    
  • allanallan Posts: 61,450Questions: 1Answers: 10,055 Site admin

    Same response on both as you see.

    Weird. The if statement that checks for the create action should mean that that code would only run when submitting a new row. When just loading the data, it shouldn't run at all.

    Could you show me your full PHP please?

    Allan

  • ztevieztevie Posts: 101Questions: 23Answers: 5

    Ok, included below...
    I've just decided to make the role access a bit different though. I will have a read-only section on the page where all logged in users have access and the other accesses will be added roles for specific users.
    It was a pretty easy change and hence there'll be no need to require at least one checked box.
    Hope something good came out of this thread though? I don't wanna waste your time, but maybe it gave you some ideas for future validation options?
    Thanks!!

    <?php
     
    // DataTables PHP library
    include( "../php/DataTables.php" );
     
    // Alias Editor classes so they are easy to use
    use
        DataTables\Editor,
        DataTables\Editor\Field,
        DataTables\Editor\Format,
        DataTables\Editor\Mjoin,
        DataTables\Editor\Upload,
        DataTables\Editor\Validate;
    
    if ( isset( $_POST['action'] ) && $_POST['action'] === 'create' ) {
      if ( ! isset( $_POST['data']['roles'] ) ) {
        echo json_encode( [
           "fieldErrors" => [
             "roles[].role_id" => "Please select at least one checkbox"
            ]
        ] );
        exit(0);
      }
    }
    
    /*
     * Example PHP implementation used for the join.html example
     */
    Editor::inst( $db, 'users', 'user_id' )
        ->field(
            Field::inst( 'users.user_pwd' )
                ->set( Field::SET_CREATE )
                ->get(false),
            Field::inst( 'users.user_employee' )
                ->validator( function ( $val, $data, $opts ) {
                    if($val === null){
                        return true;
                    }
                    else{
                        return !preg_match("/^[0-9]{6}\z/", $val) ? 'Not valid!' : true;
                    }
                } )
                ->validator( 'Validate::unique', array(
                'message' => 'Användaren existerar redan...'
                ) ),
            Field::inst( 'users.user_fname' )
                ->validator( 'Validate::notEmpty' ),
            Field::inst( 'users.user_lname' )
                ->validator( 'Validate::notEmpty' ),
            Field::inst( 'users.user_nickname' )
                ->validator( 'Validate::notEmpty' ),
            Field::inst( 'users.workteam_id' )
                ->options( 'workteam', 'workteam_id', 'workteam_name' ),
            Field::inst( 'workteam.workteam_name' ),
            Field::inst( 'users.workdesc_id' )
                ->validator( 'Validate::notEmpty' )
                ->options( 'workdesc', 'workdesc_id', 'workdesc_name' ),
            Field::inst( 'workdesc.workdesc_name' )
        )
        ->leftJoin( 'workteam', 'workteam.workteam_id', '=', 'users.workteam_id' )
        ->leftJoin( 'workdesc', 'workdesc.workdesc_id', '=', 'users.workdesc_id' )
        ->join(
            Mjoin::inst( 'roles' )
                ->link( 'users.user_id', 'permissions.user_id' )
                ->link( 'roles.role_id', 'permissions.role_id' )
                ->order( 'role_desc asc' )
                ->fields(
                    Field::inst( 'role_id' )
                        ->options( 'roles', 'role_id', 'role_desc' ),
                    Field::inst( 'role_desc' )
                )
        )
        ->on( 'preCreate', function ( $editor, $values ) {
            $editor
                ->field( 'users.user_pwd' )
                ->setValue(password_hash('qwe345', PASSWORD_DEFAULT));
        } )
        ->process($_POST)
        ->json();
    ?>
    
This discussion has been closed.