Adding/Deleting Rows in TableKit Tables Revisited

TableKit is a great javascript library for making your HTML tables fully editable. However, one problem is that you can’t add or delete rows from the tables…

I came up with a solution to this not so long ago, but it still had a problem - TableKit caches the tables on loading, so after we update the table body (adding or deleting a row) the sorting and editing of the table is completely screwed! :( However, with a little more work, and some help from one of the guys in the office - we’ve finally got this cracked! :)

The basic idea to the fix is, instead of updating the table body, replace the entire table with a new one and get TableKit to do its stuff on the new table. Although this is not the ideal solution1 - this does work quite well. Here’s the details.2

First, we’ll update the code from our template file (/root/src/users/list.tt):

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
[% META title = 'User List' -%]
 
<div id="users_div">
  <table id="users" class="sortable resizable editable">
    <thead>
      <tr>
        <th id="id" class="sortfirstasc noedit">Id</th>
        <th id="firstname">First Name</th>
        <th id="lastname">Last Name</th>
        <th class="noedit nocol"></th>
      </tr>
    </thead>
    <tfoot>
      <tr>
        <th>Id</th>
        <th>First Name</th>
        <th>Last Name</th>
        <th class="nocol"></th>
      </tr>
    </tfoot>
    <tbody id="user_body">
    [% FOREACH user IN users -%]
      <tr id='[% user.id %]'>
        <td>[% user.id %]</td>
        <td>[% user.firstname %]</td>
        <td>[% user.lastname %]</td>
        <td class="nocol">
          <a class="delete" href="#" onclick="deleteUser([% user.id %]); return false">delete</a>
        </td>
      </tr>
    [% END -%]
    </tbody>
  </table>
</div>
<br />
<a class="add" href="#" onclick="addUser(); return false">add a user</a>

Then add this javascript code to the bottom of the same file:

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
<script type="text/javascript" charset="utf-8">
  var users_table = new TableKit( 'users', {
    editAjaxURI: '[% c.uri_for('/users/_update_user') %]'
  });
 
  function addUser() {
    var timestamp = new Date().getTime();
    var new_table = 'users' + timestamp;
    var url = '[% c.uri_for('/users/_add_user') %]?timestamp=' + timestamp;
    new Ajax.Updater( 'users_div', url, { 
      asynchronous: true,
      onComplete: function() {
        new TableKit( new_table, {
          editAjaxURI: '[% c.uri_for('/users/_update_user') %]'
        })
      }
    });
  }
 
  function deleteUser( user_id ) {
    var timestamp = new Date().getTime();
    var new_table = 'users' + timestamp;
    var url = '[% c.uri_for('/users/_delete_user/') %]?user_id=' + user_id + '&timestamp=' + timestamp;
    var answer = confirm('Are you sure you want to delete this user?');
    if (answer) {
      new Ajax.Updater( 'users_div', url, { 
        asynchronous: true,
        onComplete: function() {
          new TableKit( new_table, {
            editAjaxURI: '[% c.uri_for('/users/_update_user') %]'
          })
        }
      });
    }
  }
</script>

The main differences in the above from last time round are as follows: - The ‘users’ table is now inside a div with the id ‘users_div’ - The link to add a user now calls a javascript function rather than an Ajax.Updater call directly. - The functions to add and delete users are essentially the same: - - They create a timestamp to individually identify the new table we are going to create (this is the most important thing here to remember)! - - They then use an Ajax.Updater call to talk to the Perl controller and generate the new table with a row added or removed. - - Once the Ajax.Updater is complete, TableKit is then called again to make our new table editable and sortable.

That’s the template sorted, now let’s look at the controller (/lib/MyApp/Controller/Users.pm):

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package MyApp::Controller::Users;
 
use strict;
use warnings;
use base 'Catalyst::Controller';
 
=head1 NAME
 
MyApp::Controller::Users - Catalyst Controller
 
=head1 DESCRIPTION
 
Catalyst Controller.
 
=head1 METHODS
 
=cut
 
=head2 index 
 
=cut
 
sub index : Private {
    my ( $self, $c ) = @_;
    $c->response->redirect('/users/list');
}
 
=head2 list
 
Fetch all user objects and pass to users/list.tt in stash to be displayed
 
=cut
 
sub list : Local {
    my ( $self, $c ) = @_;
    $c->stash->{users}    = [ $c->model('MyAppDB::Users')->all ];
    $c->stash->{template} = 'users/list.tt';
}
 
=head2 _update_user
 
Ajax method to update the users table
 
=cut
 
sub _update_user : Local {
    my ( $self, $c ) = @_;
 
    $c->model('MyAppDB::Users')->find( { id => $c->req->params->{id} } )
      ->update( { $c->req->params->{field} => $c->req->params->{value} } );
 
    $c->res->body( $c->req->params->{value} );
}
 
=head2 _delete_user
 
Ajax method to delete users from the users table
 
=cut
 
sub _delete_user : Local {
    my ( $self, $c ) = @_;
 
    # Look-up our user entry
    my $user =
      $c->model('MyAppDB::Users')->find( { id => $c->req->params->{user_id} } )
      ->delete();
 
    my $output =
      __return_all_users( $self, $c, $c->req->params->{timestamp}, 'null' );
 
    $c->res->body($output);
}
 
=head2 _add_user
 
Ajax method to add users to the users table
 
=cut
 
sub _add_user : Local {
    my ( $self, $c ) = @_;
 
    # create a new user...
    my $new_user = $c->model('MyAppDB::Users')->create(
        {
            id        => $c->req->params->{user_id},
            firstname => '[First Name]',
            lastname  => '[Last Name]'
        }
    );
 
    my $output = __return_all_users( $self, $c, $c->req->params->{timestamp},
        $new_user->id );
 
    $c->res->body($output);
}
 
=head2 __return_all_users
 
Private method for the Users ajax interaction.
Returns a html table.
 
=cut
 
sub __return_all_users : Private {
    my ( $self, $c, $timestamp, $new_user_id ) = @_;
 
    my @users = $c->model('MyAppDB::Users')->all();
 
    my $html =
        '<table id="users'
      . $timestamp
      . '" class="sortable resizable editable">
      <thead>
        <tr>
          <th id="id" class="sortfirstasc noedit">Id</th>
          <th id="firstname">First Name</th>
          <th id="lastname">Last Name</th>
          <th class="noedit nocol"></th>
        </tr>
      </thead>
      <tfoot>
        <tr>
          <th>Id</th>
          <th>First Name</th>
          <th>Last Name</th>
          <th class="nocol"></th>
        </tr>
      </tfoot>
      <tbody id="user_body">';
 
    if ( scalar(@users) > 0 ) {
        for ( my $i = 0 ; $i < scalar(@users) ; $i++ ) {
            my $class;
 
            if ( $new_user_id && $users[$i]->id == $new_user_id ) {
                $class = 'new';
            }
            else {
                if   ( $i % 2 == 0 ) { $class = 'rowodd'; }
                else                 { $class = 'roweven'; }
            }
 
            $html .=
                "<tr class=\"" 
              . $class
              . "\" id=\""
              . $users[$i]->id . "\">
                <td>" . $users[$i]->id . "</td>
                <td>" . $users[$i]->firstname . "</td>
                <td>" . $users[$i]->lastname . "</td>
                <td class=\"nocol\">
                    <a class=\"delete\" href=\"#\" onclick=\" deleteUser("
              . $users[$i]->id . "); return false\">delete</a>
                </td>
              </tr>";
        }
    }
    else {
        $html .=
          '<tr><td colspan="4" class="nocol">All Users Deleted</td></tr>';
    }
 
    $html .= '</tbody></table>';
 
    return ($html);
}
 
=head1 AUTHOR
 
Darren Oakley
 
=head1 LICENSE
 
This library is free software, you can redistribute it and/or modify
it under the same terms as Perl itself.
 
=cut
 
1;

The only real changes in the controller are a general code clean-up, and a slight change to the ‘__return_all_users’ method - this now returns a whole table (identified by the unique timestamped id that we generated in the javascript functions). Nothing really needed changing in the back-end.

There you go! Now our TableKit based tables can be used to not only edit entries in the database, we can now add and remove them! :)

The files used in this example can be found below.

Attached Files:

MyApp.zip - v3


  1. That would be knowing more about javascript and the dom so that we can make changes to TableKits cache as we update the table body (thus keeping the original table). 

  2. We’re using the code from the previous post as a starting point. 

7 Responses to “Adding/Deleting Rows in TableKit Tables Revisited”


  1. 1 ethan

    Hi, can you have a simple version for php ? i wish to code your example in php , hope you can share.. thanks

  2. 2 Daz

    Hi, can you have a simple version for php ? i wish to code your example in php , hope you can share.. thanks

    Unfortunatley I don’t have any examples of this in php - all of my work with this so far has been in Perl and Catalyst, but i’m sure it would be easy to reimpliment in php.

    The basic principle is that you have a several functions/scripts each with the following purposes:

    • Initial script to produce the first table.
    • One for updating individual entries via TableKit
    • A helper function that regenerates the table (for insertion via and Ajax.Updater) using a timestamp to ensure that the id of the table is unique.
    • One for deleting an entry from the database (given an id) and then calls the helper function to re-generate the table.
    • And one for adding an entry to the database and then calls the helper function to re-generate the table.

    After that, it’s all about the javascript (that is detailed above).

    If I get time I may have a look into putting something together in php, but there’s no promises…

  3. 3 ethan

    thanks for the guide. I have implemented the php version and right now i am working on the pagination , it seem to work well, and most important, thanks for your hints that enable me to make it work.

    Cheers….

  4. 4 Colin

    thanks for the guide. I have implemented the php version and right now i am working on the pagination , it seem to work well, and most important, thanks for your hints that enable me to make it work.

    Cheers….

    Hi Ethan,

    Care to share your code? I’m trying to do the same thing! I may get there myself in time but if you’ve done it already it would be nice!

    Thanks to Daz for his work too. I got a similar thing set up with the OpenRico grid, which is good, but doesn’t have the
    ‘edit-in-place’ functionality.

    Cheers,

    Colin

  5. 5 Colin

    Ok, forget it, I got it sorted! Took a while, as I thought, but I got there :-) Thanks again to Daz for the code examples.

    Colin

  6. 6 Mayank

    Ok, forget it, I got it sorted! Took a while, as I thought, but I got there :-) Thanks again to Daz for the code examples.

    Colin

    Please can you share the php code…i am trying to do the same thing for a few days.

    Thanks

  7. 7 Justin

    I had a similar issue with TableKit, but it presented itself differently. After I created the initial table when I returned a new table with Ajax.Updater it would run javascript which would first check the window for the existence of my table object. In your case I would be looking for window.users_table and attempt to call the sort method on that object thinking it would resort the data. I kept getting errors that different variables in the object were empty since it would lose its reference to the table.

    This issue has arisen for me quite often with other javascript objects which reference an ID that is later updated using Prototype’s Ajax.Updater method. It seems that Prototype does not append the data which is returned, but rather updates the innerHTML of the id which you provide. This makes sense since not all returned data will need to be appended to the DOM because it may be JSON, plain text, or something other then HTML. To fix similar issues I have moved away from using Ajax.Updater and instead used Ajax.Request with the onComplete method set to append the data which is returned as a child of the target id. This adds the returned HTML to the DOM making accessible in the manner you would expect. This may help to update the table in a more predictable and reusable manner. I have not tried this will TableKit, but I thought I would offer this a hint that may help move towards a solution that reuses the table instead of creating new one each time you update.

    Moving forward it would also be nice if TableKit was able to rebuild the table body using JSON data retrieved via AJAX so it could perform more light weight updates of larger datasets.

Leave a Reply