zen of coding

CakePHP 3 … growing up (step 4 — it’s AJAX time)

(Get the app code on github.)

Taking a look back at where I have left off, it’s time to start working on the interactive part of our application. It should come as no surprise that most of the actions taken in the app will be AJAX-based. In other words, submitting a form or clicking a checkbox will be handled by jQuery and in turn will trigger something on the server.

You can imagine that when submitting the form, I will be “adding” a to-do to the database, therefore calling:

TodosController::add()

Likewise, when I finish the task, by clicking on the checkbox, the following call should be made:

TodosController::finish($todoId)

All of this is handled by “attaching” jQuery events to the elements such as form and checkbox. This allows me to fire off ajax requests as necessary and trigger various actions in the controller.

If you recall back in part 3 we’ve included app.js file in our application.

Let’s take a look at it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
var TodoApp = {};

(function(){
  TodoApp.getTodos = function() { $.get('/todos/get.json', function(response) {
    $label = $('#incomplete-label');
    $incompleteDiv = $('#incomplete-to-dos');
    $incompleteDiv.empty();
    if (response.todos.length === 0) {
      $label.hide();
      $incompleteDiv.append('<div class="incomplete-todo">All done. Have a nice day (or add a new to-do above).</div>');
    } else {
      $label.show();
      $.each(response.todos, function(key, value) {
        $incompleteDiv.append('<div class="incomplete-todo" id="incomplete-' + value.id +'"><label for="todo_' + value.id + '">' + value.todo + ' <input id="todo_' + value.id + '" class="todo-checked" type="checkbox" /></label><div class="small-done">' + value.created + '</div></div>');
        $incompleteDiv.show('highlight');
      });
    }
  });
};

  TodoApp.getDone = function() { $.get('/todos/get/1.json', function(response) {
      $doneDiv = $('#done');
      $doneDiv.empty();
      $.each(response.todos, function(key, value) {
        $doneDiv.append('<div class="finished-task"><div class="finshed-task-text">' + value.todo + '</div><div class="small">' + value.updated + '</div></div>');
      });
    });
  };

  TodoApp.finishTask = function(id) {
    $.get('/todos/finish/' + id + '.json',
      function(response) {
        if (response.response.result == 'success') {
          $('#incomplete-' + id).hide('explode');
          $('#incomplete-' + id).remove();
          TodoApp.getTodos();
          TodoApp.getDone();
        } else if (response.response.result == 'fail') {
          console.log('fail');
        }
      }
    );
  };

})();

(function($) {
  $("#add-to-do").submit(function(event) {
    $('#todo-error').remove();
    $('.form-group').removeClass('has-error');
    var $form = $(this),
      todo = $form.find("input[name='to-do']").val(),
      url = $form.attr('action');

    var posting = $.post( url, { todo : todo } );
    posting.done(function( response ) {
      if (response.response.result == 'success') {
        $('#incomplete-to-dos').empty();
        $('#inputLarge').val('');
        TodoApp.getTodos();
      } else if (response.response.result == 'fail') {
        $('.form-group').addClass('has-error');
        $('#task-input').append('<div class="error" id="todo-error">' + response.response.error.todo + '</div>');
      }
    });
    event.preventDefault();
  });

  $(document).on('click', ':checkbox', function() {
    var id = $(this).attr('id').replace('todo_', '');
    TodoApp.finishTask(id);
  });

  TodoApp.getTodos();
  TodoApp.getDone();
})(jQuery);

There’s a little too much code here to go over everything, but I will point out the places of interaction between our front-end (jQuery) and back-end (CakePHP 3).

First, there are a couple of functions that I need to have:

getTodos() //line 4
getDone() //line 21
finishTask() //line 30

All of these functions will call our controller actions.

Very importantly, on line 48:

$("#add-to-do").submit()

We submit our form (which is really a single input field) with the name of the “to-do” that is going to be stored in the DB.
Another important thing to point out here (lines 61 – 63), is that I will rely on the response from the server to display the error messages, this allows me to leverage CakePHP’s server-side validation without any effort.

Finally on line 69 we have:

$(document).on('click', ':checkbox' ...

This binds the “click” event to all the checkboxes in the browser window. Because it’s only a one-page app and the only purpose a checkbox is serving is to “check off” or “finish” a to-do, then I can use this is method as a fast way to attach an event to any checkbox. Furthermore, you can see that when the checkbox is actually clicked, the finishTask() function is called (line 71):

TodoApp.finishTask(id);

In turn, this function triggers our controller TodosController::finish($todoId)

Overall, you can think of this file as a dispatcher of sorts.

When ceratain events are triggered (i.e. click, submit, page load) the app.js + some jQuery magic tells our server to kick-off a process, such as data validation, and then, hopefully, storage into the Database. Or, in the case of clicking a checkbox to “finish” a to-do, we send the request to the server via the finishTask() function and eventually update the record in the DB (I’ll show you this part in the following post).

Of course, in order to do all that, we need to build the rest of the application. The server-side of things. The part that interacts with our Database and sends the information (such as validation errors, or a list of tasks which are still incomplete) back to to the browser… and that’s where CakePHP comes in. Stay tuned.

%d bloggers like this: