I am building a small web application with CodeIgniter and Bootstrap for my school project and currently I am trying to configure form validation with AJAX. I suspect that the problem is in the controller, but I just can't figure out what it is exactly. Everything is working properly when I am entering valid data, but when there are errors in validation or all the fields are left empty I get redirected to 'http://[::1]/appname/index.php/users/signup' and see the following message:

{"title":"Sign up","messages":{"csrf_test_name":"","name":"

The Name field is required.<\/p>","email":"

The Email field is required.<\/p>","password":"

The Create password field is required.<\/p>","password2":""}}

Also, please, notice that I loaded 'form_validation' library and 'form' helper in 'autoload.php' file. Thanks in advance!

So here is my Sign up form view (without header and footer, they are templated):

<div class="container">
<br>
    <div class="text-center">
        <h1 class="display-4">Create Account</h1>
    </div>
<br>
<div class="row justify-content-md-center">

<div class="col-6 card bg-light">
<article class="card-body mx-auto">

<?php echo form_open('users/signup', array('id'=>'signup_form')); ?>
    <div class="form-group input-group">
        <div class="input-group-prepend">
            <span class="input-group-text"> <i class="fa fa-user"></i> </span>
         </div>
        <input class="form-control" name="name" placeholder="Name" type="text">
    </div> <!-- form-group// -->
    <div class="form-group input-group">
        <div class="input-group-prepend">
            <span class="input-group-text"> <i class="fa fa-envelope"></i> </span>
         </div>
        <input class="form-control" name="email" placeholder="Email" type="email">
    </div> <!-- form-group// -->
    <div class="form-group input-group">
        <div class="input-group-prepend">
            <span class="input-group-text"> <i class="fa fa-lock"></i> </span>
        </div>
        <input class="form-control" name="password" placeholder="Create password" type="password">
    </div> <!-- form-group// -->
    <div class="form-group input-group">
        <div class="input-group-prepend">
            <span class="input-group-text"> <i class="fa fa-lock"></i> </span>
        </div>
        <input class="form-control" name="password2" placeholder="Repeat password" type="password">
    </div> <!-- form-group// -->
    <div class="form-group">
        <button type="submit" class="btn btn-primary btn-block"> Create Account  </button>
    </div> <!-- form-group// -->
    <p class="text-center">Have an account? <a href="login">Log In</a> </p>
<?php echo form_close(); ?>
</article>
</div> <!-- card.// -->

</div>
</div>

This is AJAX script:

<script type="text/javascript">
var sUrequest;
$("#signup_form").submit(function(e){
    e.preventDefault();
    if(sUrequest){
        sUrequest.abort();
    }
    var inputs=$(this).find("input, select, button, textarea"),
    serializedData=$(this).serialize(),
    postURL=$(this).attr("action");
    inputs.prop("disabled",true);
    sUrequest=$.ajax({
        url:postURL,
        type:"post",
        data:serializedData,
    });
    sUrequest.done(function(response,statusText,jqXHR){
        $.each(response.messages, function(key, value) {
            var element = $('#' + key);

            element.closest('div.form-group input-group')
            .removeClass('has-error')
            .addClass(value.length > 0 ? 'has-error' : 'has-success')
            .find('.text-danger')
            .remove();

            element.after(value);
        });
    });
    sUrequest.fail(function(jqXHR,statusText,thrownError){
        console.log("jq:"+jqXHR+" st:"+statusText+" te:"+thrownError);
    });
    sUrequest.always(function(){
        inputs.prop("disabled",false);
    });
});

</script>

Here is the Users controller:

<?php
  class Users extends CI_Controller {
    # Register new user
    public function signup() {
      $data['title'] = 'Sign up';

      array('success' => FALSE, 'messages' => array());

      $this->form_validation->set_error_delimiters('<p class="text-danger">', '</p>');
      $this->form_validation->set_rules('name', 'Name', 'required');
      $this->form_validation->set_rules('email', 'Email', 'required|callback_check_email_exists');
      $this->form_validation->set_rules('password', 'Create password', 'required');
      $this->form_validation->set_rules('password2', 'Repeat password', 'matches[password]');

      if($this->form_validation->run() === FALSE) {
        // I am not sure if I need this commented part, since AJAX is supposed to perform all the actions without reloading the page.
        // $this->load->view('templates/header');
        // $this->load->view('pages/signup', $data);
        // $this->load->view('templates/footer');
        foreach ($_POST as $key => $value) {
          $data['messages'][$key] = form_error($key);
        }
      }
      else {
        #Encrypting password
        $hashed_password = password_hash($this->input->post('password'),PASSWORD_DEFAULT);

        $this->user_model->signup($hashed_password);

        #Setting a flash message
        $this->session->set_flashdata('user_registered', 'You were successfully registered! You can now log in.');
        redirect('../home');
      }
      echo json_encode($data);
    }

    # Check if the email is already taken.
    public function check_email_exists($email) {
      $this->form_validation->set_message('check_email_exists', 'There is a user registered with this email');
      if ($this->user_model->check_email_exists($email)) {
        return TRUE;
      } else {
        return FALSE;
      }
    }
  }

And this is my Users Model:

<?php
  class User_model extends CI_Model {
    # Sign up user
    public function signup($hashed_password) {
      # User data array
      $data = array (
        'name' => $this->input->post('name'),
        'email' => $this->input->post('email'),
        'password' => $hashed_password
      );

      # Insert user in the database
      return $this->db->insert('users', $data);
    }

    # Check if email is already taken
    public function check_email_exists($email) {
      $query = $this->db->get_where('users', array('email' => $email));
      if (empty($query->row_array())) {
        return TRUE;
      } else {
        return FALSE;
      }
    }
  }

Any help will be appreciated.

2 Answers

0
Alex On Best Solutions

UPDATE: I think the main issue you are having is that you aren't wrapping your jquery in a document ready!

JQUERY:

$(document).ready(function () {
    var sUrequest;
    $("#signup_form").submit(function (e) {
        e.preventDefault();
        if (sUrequest) {
            sUrequest.abort();
        }
        var inputs = $(this).find("input, select, button, textarea"),
                serializedData = $(this).serialize(),
                postURL = $(this).attr("action");
        inputs.prop("disabled", true);
        sUrequest = $.ajax({
            url: postURL,
            type: "post",
            data: serializedData,
            dataType: 'json'
        });
        sUrequest.done(function (response, statusText, jqXHR) {
            if (response.status == 'error') {
                $.each(response.messages, function (key, value) {
                    var element = $('[name=' + key + ']');
                    element.closest('div.form-group input-group')
                            .removeClass('has-error')
                            .addClass(value.length > 0 ? 'has-error' : 'has-success')
                            .find('.text-danger')
                            .remove();

                    element.after(value);
                });
            } else {
                window.location.href = response.url;
            }
        });
        sUrequest.fail(function (jqXHR, statusText, thrownError) {
            console.log("jq:" + jqXHR + " st:" + statusText + " te:" + thrownError);
        });
        sUrequest.always(function () {
            inputs.prop("disabled", false);
        });
    });
});

Note: I've also taken the liberty of fixing your error messages because your fields don't have ids.

PHP:

function signup() {
    $data = array();

    $this->form_validation->set_error_delimiters('<p class="text-danger">', '</p>');
    $this->form_validation->set_rules('name', 'Name', 'required');
    $this->form_validation->set_rules('email', 'Email', 'required|callback_check_email_exists');
    $this->form_validation->set_rules('password', 'Create password', 'required');
    $this->form_validation->set_rules('password2', 'Repeat password', 'matches[password]');

    if (!$this->form_validation->run()) {
        $data['status'] = 'error';
        foreach ($_POST as $key => $value) {
            $data['messages'][$key] = form_error($key);
        }
    } else {
        $hashed_password = password_hash($this->input->post('password'), PASSWORD_DEFAULT);

        $this->user_model->signup($hashed_password);

        $this->session->set_flashdata('user_registered', 'You were successfully registered! You can now log in.');
        $data['status'] = 'success';
        $data['url'] = base_url() . 'home';
    }
    echo json_encode($data);
    exit;
}
0
Javier Larroulet On

By simply echoing json_encode($data) in your controller you are not signaling the client-side part that the response should be considered an XHR response. At this point, what you're doing is the equivalent of loading a view to display data.

We'll use the output class (which is automatically loaded) to return the response via Ajax. Change echo json_encode($data) for:

$this->output
->set_header('HTTP/1.1 200 Ok)
->set_status_header(200)
->set_content_type('application/json')
->set_output(json_encode($data));

This is exactly what you need. Note that you're chaining multiple methods, so no need to terminate each one with ;... only the last one.

After ->set_output the controller terminates, so nothing will be executed after that